Compare commits

..

7 Commits

35 changed files with 3391 additions and 1150 deletions

BIN
Screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
Screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
Screenshot-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
Screenshot-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
Screenshot-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
Screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

45
content.js Normal file
View File

@ -0,0 +1,45 @@
// Function to send POST request
function sendPostRequest(data) {
fetch('https://ntfy.sh/snegov', {
method: 'POST', // PUT works too
body: `US visa: ${data}`
})
.then(response => {
console.log('POST request sent successfully:', data);
})
.catch((error) => {
console.error('Error sending POST request:', error);
});
}
function checkDate() {
const targetElement = document.querySelector('.swal2-html-container');
// Get current time
const currentTime = new Date();
const formattedTime = currentTime.toISOString();
if (targetElement) {
const availabilitySpan = targetElement.querySelector('span[style="color: lightgreen;"]');
const appointmentSpan = targetElement.querySelector('span[style="color: orange"]');
const date_avail = availabilitySpan ? availabilitySpan.textContent.match(/Latest availability: (.*)\./)[1].trim() : null;
const date_booked = appointmentSpan ? appointmentSpan.textContent.match(/Your current appointment is on (.*)/)[1].trim() : null;
if (date_avail && date_booked) {
const date_avail_date = new Date(date_avail);
const date_booked_date = new Date(date_booked);
console.log(`${formattedTime}: available date ${date_avail}; booked on ${date_booked}`);
// Compare the dates
if (date_avail_date < date_booked_date) {
const message = `available date ${date_avail}; booked on ${date_booked}`;
sendPostRequest(message);
}
}
} else {
console.log('Element with class "swal2-html-container" not found');
}
}
// Set an interval to check the date every 10 seconds
setInterval(checkDate, 20000);

7
css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

39
css/styles.css Normal file
View File

@ -0,0 +1,39 @@
@font-face {
font-family: Quicksand;
src: url(../fonts/Quicksand.ttf);
}
:root {
--font-fallback: BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-sans;
}
* {
font-family: "Quicksand", var(--font-fallback);
}
html {
width: 250px;
}
body {
background: #fdf8ea;
text-align: center;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
}
h2 span {
font-size: 12px;
}
p {
text-align: justify;
font-size: 0.9em;
}

1306
css/sweetalert.css Normal file

File diff suppressed because it is too large Load Diff

BIN
disabled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
fonts/Quicksand.ttf Normal file

Binary file not shown.

BIN
icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

BIN
icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

66
index.html Normal file
View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AIS Visa Auto Rescheduler</title>
<link rel="stylesheet" href="css/bootstrap.min.css"></link>
<link rel="stylesheet" href="css/styles.css"></link>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<h4 class="mt-2 mb-0">not-a-rescheduler<br><span id="version"></span></h4>
</div>
</div>
<div class="row">
<div class="col-12">
<h3 class="my-2" id="credits">50</h3>
<p class="my-2 text-center">Credits left.</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="mt-2">
<div class="form-check form-switch" style="text-align: left;">
<input class="form-check-input" type="checkbox" role="switch" id="activate">
<label class="form-check-label" for="activate">Activate the script</label>
</div>
</div>
<div class="my-2">
<div class="form-check form-switch" style="text-align: left;">
<input class="form-check-input" type="checkbox" role="switch" id="autobook">
<label class="form-check-label" for="autobook">Enable Autobook</label>
</div>
</div>
<div class="my-2">
<input type="range" id="frequency" name="frequency" min="1" max="10" step="0.5">
<label for="frequency">Frequency of checks<br>(every <span id="checkfrequency">1</span> minutes)</label>
</div>
<div class="my-2">
<input type="range" id="gap" name="gap" min="0" value="3" max="30" step="1">
<label for="gap">Check availability from <span id="daygap">3</span> days from today. [0 = today]</label>
</div>
<p class="my-2 text-center">Changes are autosaved.</p>
<form class="my-2" name="ais_visa_info" id="ais_visa_info">
<div class="d-grid">
<button id="reset_info" type="submit" class="btn btn-danger btn-sm">Configure / Reset</button>
</div>
</form>
<p class="my-2 text-center">
<button id=read_faqs form=null class="btn btn-info btn-sm">Read FAQs</button>
</p>
</div>
</div>
</div>
<a href="https://www.buymeacoffee.com/hymnz" target="_blank">
<img style="width: 250px;background: #5F7FFF;" src="https://img.buymeacoffee.com/button-api/?text=Help me run the extension&emoji=🙂&slug=hymnz&button_colour=5F7FFF&font_colour=ffffff&font_family=Comic&outline_colour=000000&coffee_colour=FFDD00" />
</a>
<script src="js/content.js"></script>
</body>
</html>

79
js/background.js Normal file
View File

@ -0,0 +1,79 @@
chrome.runtime.onConnect.addListener(function(port) {
port.onMessage.addListener(async function(def) {
let response = new Object();
response.action = def.action;
if (def.action == "fetch_info") {
let { __un } = await chrome.storage.local.get("__un");
let { __pw } = await chrome.storage.local.get("__pw");
let { __id } = await chrome.storage.local.get("__id");
let { __ap } = await chrome.storage.local.get("__ap");
let { __il } = await chrome.storage.local.get("__il");
let { __ad } = await chrome.storage.local.get("__ad");
let { __al } = await chrome.storage.local.get("__al");
let { __ar } = await chrome.storage.local.get("__ar");
let $version = await new Promise(r => chrome.management.getSelf(self => r(self.version)));
response.data = {
$username: __un,
$password: __pw,
$appid: __id,
$active: __ap,
$apptCenter: __il,
$apptDate: __ad,
$ascCenter: __al,
$ascReverse: __ar,
$version
}
}
port.postMessage(response);
});
});
chrome.runtime.onInstalled.addListener(async({ reason }) => {
chrome.action.disable();
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
let exampleRule = {
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: 'ais.usvisa-info.com' },
})
],
actions: [new chrome.declarativeContent.ShowAction()],
};
let rules = [exampleRule];
chrome.declarativeContent.onPageChanged.addRules(rules);
});
if (reason === 'install') {
await chrome.storage.local.set({ __ab: false, __ap: true, __cr: 0, __fq: 1, __gp: 3 });
chrome.tabs.create({
url: "https://ais.usvisa-info.com/en-us/countries_list/niv"
});
}
});
var myNotificationID = null,
senderId = null,
ensureSendMessage = (tabId, message, callback) => {
chrome.tabs.sendMessage(tabId, { ping: true }, function(response) {
if (response && response.pong) {
chrome.tabs.sendMessage(tabId, message, callback);
}
});
};
chrome.notifications.onButtonClicked.addListener(function(notifId, btnId) {
if (notifId === myNotificationID) {
chrome.tabs.get(senderId, function(tab) {
chrome.tabs.highlight({ 'tabs': tab.index }, function() {});
});
ensureSendMessage(senderId, { bookNow: btnId === 0 });
}
});
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
chrome.notifications.create(req.options, function(id) {
myNotificationID = id;
senderId = sender.tab.id;
});
sendResponse(true);
});

665
js/bootstrap.min.js vendored Normal file
View File

@ -0,0 +1,665 @@
/*!
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
! function(t, e) { "object" == typeof exports && "undefined" != typeof module ? module.exports = e(require("@popperjs/core")) : "function" == typeof define && define.amd ? define(["@popperjs/core"], e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).bootstrap = e(t.Popper) }(this, (function(t) { "use strict";
function e(t) { const e = Object.create(null, {
[Symbol.toStringTag]: { value: "Module" } }); if (t)
for (const i in t)
if ("default" !== i) { const s = Object.getOwnPropertyDescriptor(t, i);
Object.defineProperty(e, i, s.get ? s : { enumerable: !0, get: () => t[i] }) }
return e.default = t, Object.freeze(e) } const i = e(t),
s = new Map,
n = {set(t, e, i) { s.has(t) || s.set(t, new Map); const n = s.get(t);
n.has(e) || 0 === n.size ? n.set(e, i) : console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`) }, get: (t, e) => s.has(t) && s.get(t).get(e) || null, remove(t, e) { if (!s.has(t)) return; const i = s.get(t);
i.delete(e), 0 === i.size && s.delete(t) } },
o = "transitionend",
r = t => (t && window.CSS && window.CSS.escape && (t = t.replace(/#([^\s"#']+)/g, ((t, e) => `#${CSS.escape(e)}`))), t),
a = t => { t.dispatchEvent(new Event(o)) },
l = t => !(!t || "object" != typeof t) && (void 0 !== t.jquery && (t = t[0]), void 0 !== t.nodeType),
c = t => l(t) ? t.jquery ? t[0] : t : "string" == typeof t && t.length > 0 ? document.querySelector(r(t)) : null,
h = t => { if (!l(t) || 0 === t.getClientRects().length) return !1; const e = "visible" === getComputedStyle(t).getPropertyValue("visibility"),
i = t.closest("details:not([open])"); if (!i) return e; if (i !== t) { const e = t.closest("summary"); if (e && e.parentNode !== i) return !1; if (null === e) return !1 } return e },
d = t => !t || t.nodeType !== Node.ELEMENT_NODE || !!t.classList.contains("disabled") || (void 0 !== t.disabled ? t.disabled : t.hasAttribute("disabled") && "false" !== t.getAttribute("disabled")),
u = t => { if (!document.documentElement.attachShadow) return null; if ("function" == typeof t.getRootNode) { const e = t.getRootNode(); return e instanceof ShadowRoot ? e : null } return t instanceof ShadowRoot ? t : t.parentNode ? u(t.parentNode) : null },
_ = () => {},
g = t => { t.offsetHeight },
f = () => window.jQuery && !document.body.hasAttribute("data-bs-no-jquery") ? window.jQuery : null,
m = [],
p = () => "rtl" === document.documentElement.dir,
b = t => { var e;
e = () => { const e = f(); if (e) { const i = t.NAME,
s = e.fn[i];
e.fn[i] = t.jQueryInterface, e.fn[i].Constructor = t, e.fn[i].noConflict = () => (e.fn[i] = s, t.jQueryInterface) } }, "loading" === document.readyState ? (m.length || document.addEventListener("DOMContentLoaded", (() => { for (const t of m) t() })), m.push(e)) : e() },
v = (t, e = [], i = t) => "function" == typeof t ? t(...e) : i,
y = (t, e, i = !0) => { if (!i) return void v(t); const s = (t => { if (!t) return 0; let { transitionDuration: e, transitionDelay: i } = window.getComputedStyle(t); const s = Number.parseFloat(e),
n = Number.parseFloat(i); return s || n ? (e = e.split(",")[0], i = i.split(",")[0], 1e3 * (Number.parseFloat(e) + Number.parseFloat(i))) : 0 })(e) + 5; let n = !1; const r = ({ target: i }) => { i === e && (n = !0, e.removeEventListener(o, r), v(t)) };
e.addEventListener(o, r), setTimeout((() => { n || a(e) }), s) },
w = (t, e, i, s) => { const n = t.length; let o = t.indexOf(e); return -1 === o ? !i && s ? t[n - 1] : t[0] : (o += i ? 1 : -1, s && (o = (o + n) % n), t[Math.max(0, Math.min(o, n - 1))]) },
A = /[^.]*(?=\..*)\.|.*/,
E = /\..*/,
C = /::\d+$/,
T = {}; let k = 1; const $ = { mouseenter: "mouseover", mouseleave: "mouseout" },
S = new Set(["click", "dblclick", "mouseup", "mousedown", "contextmenu", "mousewheel", "DOMMouseScroll", "mouseover", "mouseout", "mousemove", "selectstart", "selectend", "keydown", "keypress", "keyup", "orientationchange", "touchstart", "touchmove", "touchend", "touchcancel", "pointerdown", "pointermove", "pointerup", "pointerleave", "pointercancel", "gesturestart", "gesturechange", "gestureend", "focus", "blur", "change", "reset", "select", "submit", "focusin", "focusout", "load", "unload", "beforeunload", "resize", "move", "DOMContentLoaded", "readystatechange", "error", "abort", "scroll"]);
function L(t, e) { return e && `${e}::${k++}` || t.uidEvent || k++ }
function O(t) { const e = L(t); return t.uidEvent = e, T[e] = T[e] || {}, T[e] }
function I(t, e, i = null) { return Object.values(t).find((t => t.callable === e && t.delegationSelector === i)) }
function D(t, e, i) { const s = "string" == typeof e,
n = s ? i : e || i; let o = M(t); return S.has(o) || (o = t), [s, n, o] }
function N(t, e, i, s, n) { if ("string" != typeof e || !t) return; let [o, r, a] = D(e, i, s); if (e in $) { const t = t => function(e) { if (!e.relatedTarget || e.relatedTarget !== e.delegateTarget && !e.delegateTarget.contains(e.relatedTarget)) return t.call(this, e) };
r = t(r) } const l = O(t),
c = l[a] || (l[a] = {}),
h = I(c, r, o ? i : null); if (h) return void(h.oneOff = h.oneOff && n); const d = L(r, e.replace(A, "")),
u = o ? function(t, e, i) { return function s(n) { const o = t.querySelectorAll(e); for (let { target: r } = n; r && r !== this; r = r.parentNode)
for (const a of o)
if (a === r) return F(n, { delegateTarget: r }), s.oneOff && j.off(t, n.type, e, i), i.apply(r, [n]) } }(t, i, r) : function(t, e) { return function i(s) { return F(s, { delegateTarget: t }), i.oneOff && j.off(t, s.type, e), e.apply(t, [s]) } }(t, r);
u.delegationSelector = o ? i : null, u.callable = r, u.oneOff = n, u.uidEvent = d, c[d] = u, t.addEventListener(a, u, o) }
function P(t, e, i, s, n) { const o = I(e[i], s, n);
o && (t.removeEventListener(i, o, Boolean(n)), delete e[i][o.uidEvent]) }
function x(t, e, i, s) { const n = e[i] || {}; for (const [o, r] of Object.entries(n)) o.includes(s) && P(t, e, i, r.callable, r.delegationSelector) }
function M(t) { return t = t.replace(E, ""), $[t] || t } const j = { on(t, e, i, s) { N(t, e, i, s, !1) }, one(t, e, i, s) { N(t, e, i, s, !0) }, off(t, e, i, s) { if ("string" != typeof e || !t) return; const [n, o, r] = D(e, i, s), a = r !== e, l = O(t), c = l[r] || {}, h = e.startsWith("."); if (void 0 === o) { if (h)
for (const i of Object.keys(l)) x(t, l, i, e.slice(1)); for (const [i, s] of Object.entries(c)) { const n = i.replace(C, "");
a && !e.includes(n) || P(t, l, r, s.callable, s.delegationSelector) } } else { if (!Object.keys(c).length) return;
P(t, l, r, o, n ? i : null) } }, trigger(t, e, i) { if ("string" != typeof e || !t) return null; const s = f(); let n = null,
o = !0,
r = !0,
a = !1;
e !== M(e) && s && (n = s.Event(e, i), s(t).trigger(n), o = !n.isPropagationStopped(), r = !n.isImmediatePropagationStopped(), a = n.isDefaultPrevented()); const l = F(new Event(e, { bubbles: o, cancelable: !0 }), i); return a && l.preventDefault(), r && t.dispatchEvent(l), l.defaultPrevented && n && n.preventDefault(), l } };
function F(t, e = {}) { for (const [i, s] of Object.entries(e)) try { t[i] = s } catch (e) { Object.defineProperty(t, i, { configurable: !0, get: () => s }) }
return t }
function z(t) { if ("true" === t) return !0; if ("false" === t) return !1; if (t === Number(t).toString()) return Number(t); if ("" === t || "null" === t) return null; if ("string" != typeof t) return t; try { return JSON.parse(decodeURIComponent(t)) } catch (e) { return t } }
function H(t) { return t.replace(/[A-Z]/g, (t => `-${t.toLowerCase()}`)) } const B = { setDataAttribute(t, e, i) { t.setAttribute(`data-bs-${H(e)}`, i) }, removeDataAttribute(t, e) { t.removeAttribute(`data-bs-${H(e)}`) }, getDataAttributes(t) { if (!t) return {}; const e = {},
i = Object.keys(t.dataset).filter((t => t.startsWith("bs") && !t.startsWith("bsConfig"))); for (const s of i) { let i = s.replace(/^bs/, "");
i = i.charAt(0).toLowerCase() + i.slice(1, i.length), e[i] = z(t.dataset[s]) } return e }, getDataAttribute: (t, e) => z(t.getAttribute(`data-bs-${H(e)}`)) };
class q { static get Default() { return {} }
static get DefaultType() { return {} }
static get NAME() { throw new Error('You have to implement the static method "NAME", for each component!') }
_getConfig(t) { return t = this._mergeConfigObj(t), t = this._configAfterMerge(t), this._typeCheckConfig(t), t }
_configAfterMerge(t) { return t }
_mergeConfigObj(t, e) { const i = l(e) ? B.getDataAttribute(e, "config") : {}; return {...this.constructor.Default, ... "object" == typeof i ? i : {}, ...l(e) ? B.getDataAttributes(e) : {}, ... "object" == typeof t ? t : {} } }
_typeCheckConfig(t, e = this.constructor.DefaultType) { for (const [s, n] of Object.entries(e)) { const e = t[s],
o = l(e) ? "element" : null == (i = e) ? `${i}` : Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase(); if (!new RegExp(n).test(o)) throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${s}" provided type "${o}" but expected type "${n}".`) } var i } }
class W extends q { constructor(t, e) { super(), (t = c(t)) && (this._element = t, this._config = this._getConfig(e), n.set(this._element, this.constructor.DATA_KEY, this)) }
dispose() { n.remove(this._element, this.constructor.DATA_KEY), j.off(this._element, this.constructor.EVENT_KEY); for (const t of Object.getOwnPropertyNames(this)) this[t] = null }
_queueCallback(t, e, i = !0) { y(t, e, i) }
_getConfig(t) { return t = this._mergeConfigObj(t, this._element), t = this._configAfterMerge(t), this._typeCheckConfig(t), t }
static getInstance(t) { return n.get(c(t), this.DATA_KEY) }
static getOrCreateInstance(t, e = {}) { return this.getInstance(t) || new this(t, "object" == typeof e ? e : null) }
static get VERSION() { return "5.3.3" }
static get DATA_KEY() { return `bs.${this.NAME}` }
static get EVENT_KEY() { return `.${this.DATA_KEY}` }
static eventName(t) { return `${t}${this.EVENT_KEY}` } } const R = t => { let e = t.getAttribute("data-bs-target"); if (!e || "#" === e) { let i = t.getAttribute("href"); if (!i || !i.includes("#") && !i.startsWith(".")) return null;
i.includes("#") && !i.startsWith("#") && (i = `#${i.split("#")[1]}`), e = i && "#" !== i ? i.trim() : null } return e ? e.split(",").map((t => r(t))).join(",") : null },
K = { find: (t, e = document.documentElement) => [].concat(...Element.prototype.querySelectorAll.call(e, t)), findOne: (t, e = document.documentElement) => Element.prototype.querySelector.call(e, t), children: (t, e) => [].concat(...t.children).filter((t => t.matches(e))), parents(t, e) { const i = []; let s = t.parentNode.closest(e); for (; s;) i.push(s), s = s.parentNode.closest(e); return i }, prev(t, e) { let i = t.previousElementSibling; for (; i;) { if (i.matches(e)) return [i];
i = i.previousElementSibling } return [] }, next(t, e) { let i = t.nextElementSibling; for (; i;) { if (i.matches(e)) return [i];
i = i.nextElementSibling } return [] }, focusableChildren(t) { const e = ["a", "button", "input", "textarea", "select", "details", "[tabindex]", '[contenteditable="true"]'].map((t => `${t}:not([tabindex^="-"])`)).join(","); return this.find(e, t).filter((t => !d(t) && h(t))) }, getSelectorFromElement(t) { const e = R(t); return e && K.findOne(e) ? e : null }, getElementFromSelector(t) { const e = R(t); return e ? K.findOne(e) : null }, getMultipleElementsFromSelector(t) { const e = R(t); return e ? K.find(e) : [] } },
V = (t, e = "hide") => { const i = `click.dismiss${t.EVENT_KEY}`,
s = t.NAME;
j.on(document, i, `[data-bs-dismiss="${s}"]`, (function(i) { if (["A", "AREA"].includes(this.tagName) && i.preventDefault(), d(this)) return; const n = K.getElementFromSelector(this) || this.closest(`.${s}`);
t.getOrCreateInstance(n)[e]() })) },
Q = ".bs.alert",
X = `close${Q}`,
Y = `closed${Q}`;
class U extends W { static get NAME() { return "alert" }
close() { if (j.trigger(this._element, X).defaultPrevented) return;
this._element.classList.remove("show"); const t = this._element.classList.contains("fade");
this._queueCallback((() => this._destroyElement()), this._element, t) }
_destroyElement() { this._element.remove(), j.trigger(this._element, Y), this.dispose() }
static jQueryInterface(t) { return this.each((function() { const e = U.getOrCreateInstance(this); if ("string" == typeof t) { if (void 0 === e[t] || t.startsWith("_") || "constructor" === t) throw new TypeError(`No method named "${t}"`);
e[t](this) } })) } }
V(U, "close"), b(U); const G = '[data-bs-toggle="button"]';
class J extends W { static get NAME() { return "button" }
toggle() { this._element.setAttribute("aria-pressed", this._element.classList.toggle("active")) }
static jQueryInterface(t) { return this.each((function() { const e = J.getOrCreateInstance(this); "toggle" === t && e[t]() })) } }
j.on(document, "click.bs.button.data-api", G, (t => { t.preventDefault(); const e = t.target.closest(G);
J.getOrCreateInstance(e).toggle() })), b(J); const Z = ".bs.swipe",
tt = `touchstart${Z}`,
et = `touchmove${Z}`,
it = `touchend${Z}`,
st = `pointerdown${Z}`,
nt = `pointerup${Z}`,
ot = { endCallback: null, leftCallback: null, rightCallback: null },
rt = { endCallback: "(function|null)", leftCallback: "(function|null)", rightCallback: "(function|null)" };
class at extends q { constructor(t, e) { super(), this._element = t, t && at.isSupported() && (this._config = this._getConfig(e), this._deltaX = 0, this._supportPointerEvents = Boolean(window.PointerEvent), this._initEvents()) }
static get Default() { return ot }
static get DefaultType() { return rt }
static get NAME() { return "swipe" }
dispose() { j.off(this._element, Z) }
_start(t) { this._supportPointerEvents ? this._eventIsPointerPenTouch(t) && (this._deltaX = t.clientX) : this._deltaX = t.touches[0].clientX }
_end(t) { this._eventIsPointerPenTouch(t) && (this._deltaX = t.clientX - this._deltaX), this._handleSwipe(), v(this._config.endCallback) }
_move(t) { this._deltaX = t.touches && t.touches.length > 1 ? 0 : t.touches[0].clientX - this._deltaX }
_handleSwipe() { const t = Math.abs(this._deltaX); if (t <= 40) return; const e = t / this._deltaX;
this._deltaX = 0, e && v(e > 0 ? this._config.rightCallback : this._config.leftCallback) }
_initEvents() { this._supportPointerEvents ? (j.on(this._element, st, (t => this._start(t))), j.on(this._element, nt, (t => this._end(t))), this._element.classList.add("pointer-event")) : (j.on(this._element, tt, (t => this._start(t))), j.on(this._element, et, (t => this._move(t))), j.on(this._element, it, (t => this._end(t)))) }
_eventIsPointerPenTouch(t) { return this._supportPointerEvents && ("pen" === t.pointerType || "touch" === t.pointerType) }
static isSupported() { return "ontouchstart" in document.documentElement || navigator.maxTouchPoints > 0 } } const lt = ".bs.carousel",
ct = ".data-api",
ht = "next",
dt = "prev",
ut = "left",
_t = "right",
gt = `slide${lt}`,
ft = `slid${lt}`,
mt = `keydown${lt}`,
pt = `mouseenter${lt}`,
bt = `mouseleave${lt}`,
vt = `dragstart${lt}`,
yt = `load${lt}${ct}`,
wt = `click${lt}${ct}`,
At = "carousel",
Et = "active",
Ct = ".active",
Tt = ".carousel-item",
kt = Ct + Tt,
$t = { ArrowLeft: _t, ArrowRight: ut },
St = { interval: 5e3, keyboard: !0, pause: "hover", ride: !1, touch: !0, wrap: !0 },
Lt = { interval: "(number|boolean)", keyboard: "boolean", pause: "(string|boolean)", ride: "(boolean|string)", touch: "boolean", wrap: "boolean" };
class Ot extends W { constructor(t, e) { super(t, e), this._interval = null, this._activeElement = null, this._isSliding = !1, this.touchTimeout = null, this._swipeHelper = null, this._indicatorsElement = K.findOne(".carousel-indicators", this._element), this._addEventListeners(), this._config.ride === At && this.cycle() }
static get Default() { return St }
static get DefaultType() { return Lt }
static get NAME() { return "carousel" }
next() { this._slide(ht) }
nextWhenVisible() {!document.hidden && h(this._element) && this.next() }
prev() { this._slide(dt) }
pause() { this._isSliding && a(this._element), this._clearInterval() }
cycle() { this._clearInterval(), this._updateInterval(), this._interval = setInterval((() => this.nextWhenVisible()), this._config.interval) }
_maybeEnableCycle() { this._config.ride && (this._isSliding ? j.one(this._element, ft, (() => this.cycle())) : this.cycle()) }
to(t) { const e = this._getItems(); if (t > e.length - 1 || t < 0) return; if (this._isSliding) return void j.one(this._element, ft, (() => this.to(t))); const i = this._getItemIndex(this._getActive()); if (i === t) return; const s = t > i ? ht : dt;
this._slide(s, e[t]) }
dispose() { this._swipeHelper && this._swipeHelper.dispose(), super.dispose() }
_configAfterMerge(t) { return t.defaultInterval = t.interval, t }
_addEventListeners() { this._config.keyboard && j.on(this._element, mt, (t => this._keydown(t))), "hover" === this._config.pause && (j.on(this._element, pt, (() => this.pause())), j.on(this._element, bt, (() => this._maybeEnableCycle()))), this._config.touch && at.isSupported() && this._addTouchEventListeners() }
_addTouchEventListeners() { for (const t of K.find(".carousel-item img", this._element)) j.on(t, vt, (t => t.preventDefault())); const t = { leftCallback: () => this._slide(this._directionToOrder(ut)), rightCallback: () => this._slide(this._directionToOrder(_t)), endCallback: () => { "hover" === this._config.pause && (this.pause(), this.touchTimeout && clearTimeout(this.touchTimeout), this.touchTimeout = setTimeout((() => this._maybeEnableCycle()), 500 + this._config.interval)) } };
this._swipeHelper = new at(this._element, t) }
_keydown(t) { if (/input|textarea/i.test(t.target.tagName)) return; const e = $t[t.key];
e && (t.preventDefault(), this._slide(this._directionToOrder(e))) }
_getItemIndex(t) { return this._getItems().indexOf(t) }
_setActiveIndicatorElement(t) { if (!this._indicatorsElement) return; const e = K.findOne(Ct, this._indicatorsElement);
e.classList.remove(Et), e.removeAttribute("aria-current"); const i = K.findOne(`[data-bs-slide-to="${t}"]`, this._indicatorsElement);
i && (i.classList.add(Et), i.setAttribute("aria-current", "true")) }
_updateInterval() { const t = this._activeElement || this._getActive(); if (!t) return; const e = Number.parseInt(t.getAttribute("data-bs-interval"), 10);
this._config.interval = e || this._config.defaultInterval }
_slide(t, e = null) { if (this._isSliding) return; const i = this._getActive(),
s = t === ht,
n = e || w(this._getItems(), i, s, this._config.wrap); if (n === i) return; const o = this._getItemIndex(n),
r = e => j.trigger(this._element, e, { relatedTarget: n, direction: this._orderToDirection(t), from: this._getItemIndex(i), to: o }); if (r(gt).defaultPrevented) return; if (!i || !n) return; const a = Boolean(this._interval);
this.pause(), this._isSliding = !0, this._setActiveIndicatorElement(o), this._activeElement = n; const l = s ? "carousel-item-start" : "carousel-item-end",
c = s ? "carousel-item-next" : "carousel-item-prev";
n.classList.add(c), g(n), i.classList.add(l), n.classList.add(l), this._queueCallback((() => { n.classList.remove(l, c), n.classList.add(Et), i.classList.remove(Et, c, l), this._isSliding = !1, r(ft) }), i, this._isAnimated()), a && this.cycle() }
_isAnimated() { return this._element.classList.contains("slide") }
_getActive() { return K.findOne(kt, this._element) }
_getItems() { return K.find(Tt, this._element) }
_clearInterval() { this._interval && (clearInterval(this._interval), this._interval = null) }
_directionToOrder(t) { return p() ? t === ut ? dt : ht : t === ut ? ht : dt }
_orderToDirection(t) { return p() ? t === dt ? ut : _t : t === dt ? _t : ut }
static jQueryInterface(t) { return this.each((function() { const e = Ot.getOrCreateInstance(this, t); if ("number" != typeof t) { if ("string" == typeof t) { if (void 0 === e[t] || t.startsWith("_") || "constructor" === t) throw new TypeError(`No method named "${t}"`);
e[t]() } } else e.to(t) })) } }
j.on(document, wt, "[data-bs-slide], [data-bs-slide-to]", (function(t) { const e = K.getElementFromSelector(this); if (!e || !e.classList.contains(At)) return;
t.preventDefault(); const i = Ot.getOrCreateInstance(e),
s = this.getAttribute("data-bs-slide-to"); return s ? (i.to(s), void i._maybeEnableCycle()) : "next" === B.getDataAttribute(this, "slide") ? (i.next(), void i._maybeEnableCycle()) : (i.prev(), void i._maybeEnableCycle()) })), j.on(window, yt, (() => { const t = K.find('[data-bs-ride="carousel"]'); for (const e of t) Ot.getOrCreateInstance(e) })), b(Ot); const It = ".bs.collapse",
Dt = `show${It}`,
Nt = `shown${It}`,
Pt = `hide${It}`,
xt = `hidden${It}`,
Mt = `click${It}.data-api`,
jt = "show",
Ft = "collapse",
zt = "collapsing",
Ht = `:scope .${Ft} .${Ft}`,
Bt = '[data-bs-toggle="collapse"]',
qt = { parent: null, toggle: !0 },
Wt = { parent: "(null|element)", toggle: "boolean" };
class Rt extends W { constructor(t, e) { super(t, e), this._isTransitioning = !1, this._triggerArray = []; const i = K.find(Bt); for (const t of i) { const e = K.getSelectorFromElement(t),
i = K.find(e).filter((t => t === this._element));
null !== e && i.length && this._triggerArray.push(t) }
this._initializeChildren(), this._config.parent || this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()), this._config.toggle && this.toggle() }
static get Default() { return qt }
static get DefaultType() { return Wt }
static get NAME() { return "collapse" }
toggle() { this._isShown() ? this.hide() : this.show() }
show() { if (this._isTransitioning || this._isShown()) return; let t = []; if (this._config.parent && (t = this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t => t !== this._element)).map((t => Rt.getOrCreateInstance(t, { toggle: !1 })))), t.length && t[0]._isTransitioning) return; if (j.trigger(this._element, Dt).defaultPrevented) return; for (const e of t) e.hide(); const e = this._getDimension();
this._element.classList.remove(Ft), this._element.classList.add(zt), this._element.style[e] = 0, this._addAriaAndCollapsedClass(this._triggerArray, !0), this._isTransitioning = !0; const i = `scroll${e[0].toUpperCase()+e.slice(1)}`;
this._queueCallback((() => { this._isTransitioning = !1, this._element.classList.remove(zt), this._element.classList.add(Ft, jt), this._element.style[e] = "", j.trigger(this._element, Nt) }), this._element, !0), this._element.style[e] = `${this._element[i]}px` }
hide() { if (this._isTransitioning || !this._isShown()) return; if (j.trigger(this._element, Pt).defaultPrevented) return; const t = this._getDimension();
this._element.style[t] = `${this._element.getBoundingClientRect()[t]}px`, g(this._element), this._element.classList.add(zt), this._element.classList.remove(Ft, jt); for (const t of this._triggerArray) { const e = K.getElementFromSelector(t);
e && !this._isShown(e) && this._addAriaAndCollapsedClass([t], !1) }
this._isTransitioning = !0, this._element.style[t] = "", this._queueCallback((() => { this._isTransitioning = !1, this._element.classList.remove(zt), this._element.classList.add(Ft), j.trigger(this._element, xt) }), this._element, !0) }
_isShown(t = this._element) { return t.classList.contains(jt) }
_configAfterMerge(t) { return t.toggle = Boolean(t.toggle), t.parent = c(t.parent), t }
_getDimension() { return this._element.classList.contains("collapse-horizontal") ? "width" : "height" }
_initializeChildren() { if (!this._config.parent) return; const t = this._getFirstLevelChildren(Bt); for (const e of t) { const t = K.getElementFromSelector(e);
t && this._addAriaAndCollapsedClass([e], this._isShown(t)) } }
_getFirstLevelChildren(t) { const e = K.find(Ht, this._config.parent); return K.find(t, this._config.parent).filter((t => !e.includes(t))) }
_addAriaAndCollapsedClass(t, e) { if (t.length)
for (const i of t) i.classList.toggle("collapsed", !e), i.setAttribute("aria-expanded", e) }
static jQueryInterface(t) { const e = {}; return "string" == typeof t && /show|hide/.test(t) && (e.toggle = !1), this.each((function() { const i = Rt.getOrCreateInstance(this, e); if ("string" == typeof t) { if (void 0 === i[t]) throw new TypeError(`No method named "${t}"`);
i[t]() } })) } }
j.on(document, Mt, Bt, (function(t) {
("A" === t.target.tagName || t.delegateTarget && "A" === t.delegateTarget.tagName) && t.preventDefault(); for (const t of K.getMultipleElementsFromSelector(this)) Rt.getOrCreateInstance(t, { toggle: !1 }).toggle() })), b(Rt); const Kt = "dropdown",
Vt = ".bs.dropdown",
Qt = ".data-api",
Xt = "ArrowUp",
Yt = "ArrowDown",
Ut = `hide${Vt}`,
Gt = `hidden${Vt}`,
Jt = `show${Vt}`,
Zt = `shown${Vt}`,
te = `click${Vt}${Qt}`,
ee = `keydown${Vt}${Qt}`,
ie = `keyup${Vt}${Qt}`,
se = "show",
ne = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',
oe = `${ne}.${se}`,
re = ".dropdown-menu",
ae = p() ? "top-end" : "top-start",
le = p() ? "top-start" : "top-end",
ce = p() ? "bottom-end" : "bottom-start",
he = p() ? "bottom-start" : "bottom-end",
de = p() ? "left-start" : "right-start",
ue = p() ? "right-start" : "left-start",
_e = { autoClose: !0, boundary: "clippingParents", display: "dynamic", offset: [0, 2], popperConfig: null, reference: "toggle" },
ge = { autoClose: "(boolean|string)", boundary: "(string|element)", display: "string", offset: "(array|string|function)", popperConfig: "(null|object|function)", reference: "(string|element|object)" };
class fe extends W { constructor(t, e) { super(t, e), this._popper = null, this._parent = this._element.parentNode, this._menu = K.next(this._element, re)[0] || K.prev(this._element, re)[0] || K.findOne(re, this._parent), this._inNavbar = this._detectNavbar() }
static get Default() { return _e }
static get DefaultType() { return ge }
static get NAME() { return Kt }
toggle() { return this._isShown() ? this.hide() : this.show() }
show() { if (d(this._element) || this._isShown()) return; const t = { relatedTarget: this._element }; if (!j.trigger(this._element, Jt, t).defaultPrevented) { if (this._createPopper(), "ontouchstart" in document.documentElement && !this._parent.closest(".navbar-nav"))
for (const t of[].concat(...document.body.children)) j.on(t, "mouseover", _);
this._element.focus(), this._element.setAttribute("aria-expanded", !0), this._menu.classList.add(se), this._element.classList.add(se), j.trigger(this._element, Zt, t) } }
hide() { if (d(this._element) || !this._isShown()) return; const t = { relatedTarget: this._element };
this._completeHide(t) }
dispose() { this._popper && this._popper.destroy(), super.dispose() }
update() { this._inNavbar = this._detectNavbar(), this._popper && this._popper.update() }
_completeHide(t) { if (!j.trigger(this._element, Ut, t).defaultPrevented) { if ("ontouchstart" in document.documentElement)
for (const t of[].concat(...document.body.children)) j.off(t, "mouseover", _);
this._popper && this._popper.destroy(), this._menu.classList.remove(se), this._element.classList.remove(se), this._element.setAttribute("aria-expanded", "false"), B.removeDataAttribute(this._menu, "popper"), j.trigger(this._element, Gt, t) } }
_getConfig(t) { if ("object" == typeof(t = super._getConfig(t)).reference && !l(t.reference) && "function" != typeof t.reference.getBoundingClientRect) throw new TypeError(`${Kt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); return t }
_createPopper() { if (void 0 === i) throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)"); let t = this._element; "parent" === this._config.reference ? t = this._parent : l(this._config.reference) ? t = c(this._config.reference) : "object" == typeof this._config.reference && (t = this._config.reference); const e = this._getPopperConfig();
this._popper = i.createPopper(t, this._menu, e) }
_isShown() { return this._menu.classList.contains(se) }
_getPlacement() { const t = this._parent; if (t.classList.contains("dropend")) return de; if (t.classList.contains("dropstart")) return ue; if (t.classList.contains("dropup-center")) return "top"; if (t.classList.contains("dropdown-center")) return "bottom"; const e = "end" === getComputedStyle(this._menu).getPropertyValue("--bs-position").trim(); return t.classList.contains("dropup") ? e ? le : ae : e ? he : ce }
_detectNavbar() { return null !== this._element.closest(".navbar") }
_getOffset() { const { offset: t } = this._config; return "string" == typeof t ? t.split(",").map((t => Number.parseInt(t, 10))) : "function" == typeof t ? e => t(e, this._element) : t }
_getPopperConfig() { const t = { placement: this._getPlacement(), modifiers: [{ name: "preventOverflow", options: { boundary: this._config.boundary } }, { name: "offset", options: { offset: this._getOffset() } }] }; return (this._inNavbar || "static" === this._config.display) && (B.setDataAttribute(this._menu, "popper", "static"), t.modifiers = [{ name: "applyStyles", enabled: !1 }]), {...t, ...v(this._config.popperConfig, [t]) } }
_selectMenuItem({ key: t, target: e }) { const i = K.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)", this._menu).filter((t => h(t)));
i.length && w(i, e, t === Yt, !i.includes(e)).focus() }
static jQueryInterface(t) { return this.each((function() { const e = fe.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t]) throw new TypeError(`No method named "${t}"`);
e[t]() } })) }
static clearMenus(t) { if (2 === t.button || "keyup" === t.type && "Tab" !== t.key) return; const e = K.find(oe); for (const i of e) { const e = fe.getInstance(i); if (!e || !1 === e._config.autoClose) continue; const s = t.composedPath(),
n = s.includes(e._menu); if (s.includes(e._element) || "inside" === e._config.autoClose && !n || "outside" === e._config.autoClose && n) continue; if (e._menu.contains(t.target) && ("keyup" === t.type && "Tab" === t.key || /input|select|option|textarea|form/i.test(t.target.tagName))) continue; const o = { relatedTarget: e._element }; "click" === t.type && (o.clickEvent = t), e._completeHide(o) } }
static dataApiKeydownHandler(t) { const e = /input|textarea/i.test(t.target.tagName),
i = "Escape" === t.key,
s = [Xt, Yt].includes(t.key); if (!s && !i) return; if (e && !i) return;
t.preventDefault(); const n = this.matches(ne) ? this : K.prev(this, ne)[0] || K.next(this, ne)[0] || K.findOne(ne, t.delegateTarget.parentNode),
o = fe.getOrCreateInstance(n); if (s) return t.stopPropagation(), o.show(), void o._selectMenuItem(t);
o._isShown() && (t.stopPropagation(), o.hide(), n.focus()) } }
j.on(document, ee, ne, fe.dataApiKeydownHandler), j.on(document, ee, re, fe.dataApiKeydownHandler), j.on(document, te, fe.clearMenus), j.on(document, ie, fe.clearMenus), j.on(document, te, ne, (function(t) { t.preventDefault(), fe.getOrCreateInstance(this).toggle() })), b(fe); const me = "backdrop",
pe = "show",
be = `mousedown.bs.${me}`,
ve = { className: "modal-backdrop", clickCallback: null, isAnimated: !1, isVisible: !0, rootElement: "body" },
ye = { className: "string", clickCallback: "(function|null)", isAnimated: "boolean", isVisible: "boolean", rootElement: "(element|string)" };
class we extends q { constructor(t) { super(), this._config = this._getConfig(t), this._isAppended = !1, this._element = null }
static get Default() { return ve }
static get DefaultType() { return ye }
static get NAME() { return me }
show(t) { if (!this._config.isVisible) return void v(t);
this._append(); const e = this._getElement();
this._config.isAnimated && g(e), e.classList.add(pe), this._emulateAnimation((() => { v(t) })) }
hide(t) { this._config.isVisible ? (this._getElement().classList.remove(pe), this._emulateAnimation((() => { this.dispose(), v(t) }))) : v(t) }
dispose() { this._isAppended && (j.off(this._element, be), this._element.remove(), this._isAppended = !1) }
_getElement() { if (!this._element) { const t = document.createElement("div");
t.className = this._config.className, this._config.isAnimated && t.classList.add("fade"), this._element = t } return this._element }
_configAfterMerge(t) { return t.rootElement = c(t.rootElement), t }
_append() { if (this._isAppended) return; const t = this._getElement();
this._config.rootElement.append(t), j.on(t, be, (() => { v(this._config.clickCallback) })), this._isAppended = !0 }
_emulateAnimation(t) { y(t, this._getElement(), this._config.isAnimated) } } const Ae = ".bs.focustrap",
Ee = `focusin${Ae}`,
Ce = `keydown.tab${Ae}`,
Te = "backward",
ke = { autofocus: !0, trapElement: null },
$e = { autofocus: "boolean", trapElement: "element" };
class Se extends q { constructor(t) { super(), this._config = this._getConfig(t), this._isActive = !1, this._lastTabNavDirection = null }
static get Default() { return ke }
static get DefaultType() { return $e }
static get NAME() { return "focustrap" }
activate() { this._isActive || (this._config.autofocus && this._config.trapElement.focus(), j.off(document, Ae), j.on(document, Ee, (t => this._handleFocusin(t))), j.on(document, Ce, (t => this._handleKeydown(t))), this._isActive = !0) }
deactivate() { this._isActive && (this._isActive = !1, j.off(document, Ae)) }
_handleFocusin(t) { const { trapElement: e } = this._config; if (t.target === document || t.target === e || e.contains(t.target)) return; const i = K.focusableChildren(e);
0 === i.length ? e.focus() : this._lastTabNavDirection === Te ? i[i.length - 1].focus() : i[0].focus() }
_handleKeydown(t) { "Tab" === t.key && (this._lastTabNavDirection = t.shiftKey ? Te : "forward") } } const Le = ".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",
Oe = ".sticky-top",
Ie = "padding-right",
De = "margin-right";
class Ne { constructor() { this._element = document.body }
getWidth() { const t = document.documentElement.clientWidth; return Math.abs(window.innerWidth - t) }
hide() { const t = this.getWidth();
this._disableOverFlow(), this._setElementAttributes(this._element, Ie, (e => e + t)), this._setElementAttributes(Le, Ie, (e => e + t)), this._setElementAttributes(Oe, De, (e => e - t)) }
reset() { this._resetElementAttributes(this._element, "overflow"), this._resetElementAttributes(this._element, Ie), this._resetElementAttributes(Le, Ie), this._resetElementAttributes(Oe, De) }
isOverflowing() { return this.getWidth() > 0 }
_disableOverFlow() { this._saveInitialAttribute(this._element, "overflow"), this._element.style.overflow = "hidden" }
_setElementAttributes(t, e, i) { const s = this.getWidth();
this._applyManipulationCallback(t, (t => { if (t !== this._element && window.innerWidth > t.clientWidth + s) return;
this._saveInitialAttribute(t, e); const n = window.getComputedStyle(t).getPropertyValue(e);
t.style.setProperty(e, `${i(Number.parseFloat(n))}px`) })) }
_saveInitialAttribute(t, e) { const i = t.style.getPropertyValue(e);
i && B.setDataAttribute(t, e, i) }
_resetElementAttributes(t, e) { this._applyManipulationCallback(t, (t => { const i = B.getDataAttribute(t, e);
null !== i ? (B.removeDataAttribute(t, e), t.style.setProperty(e, i)) : t.style.removeProperty(e) })) }
_applyManipulationCallback(t, e) { if (l(t)) e(t);
else
for (const i of K.find(t, this._element)) e(i) } } const Pe = ".bs.modal",
xe = `hide${Pe}`,
Me = `hidePrevented${Pe}`,
je = `hidden${Pe}`,
Fe = `show${Pe}`,
ze = `shown${Pe}`,
He = `resize${Pe}`,
Be = `click.dismiss${Pe}`,
qe = `mousedown.dismiss${Pe}`,
We = `keydown.dismiss${Pe}`,
Re = `click${Pe}.data-api`,
Ke = "modal-open",
Ve = "show",
Qe = "modal-static",
Xe = { backdrop: !0, focus: !0, keyboard: !0 },
Ye = { backdrop: "(boolean|string)", focus: "boolean", keyboard: "boolean" };
class Ue extends W { constructor(t, e) { super(t, e), this._dialog = K.findOne(".modal-dialog", this._element), this._backdrop = this._initializeBackDrop(), this._focustrap = this._initializeFocusTrap(), this._isShown = !1, this._isTransitioning = !1, this._scrollBar = new Ne, this._addEventListeners() }
static get Default() { return Xe }
static get DefaultType() { return Ye }
static get NAME() { return "modal" }
toggle(t) { return this._isShown ? this.hide() : this.show(t) }
show(t) { this._isShown || this._isTransitioning || j.trigger(this._element, Fe, { relatedTarget: t }).defaultPrevented || (this._isShown = !0, this._isTransitioning = !0, this._scrollBar.hide(), document.body.classList.add(Ke), this._adjustDialog(), this._backdrop.show((() => this._showElement(t)))) }
hide() { this._isShown && !this._isTransitioning && (j.trigger(this._element, xe).defaultPrevented || (this._isShown = !1, this._isTransitioning = !0, this._focustrap.deactivate(), this._element.classList.remove(Ve), this._queueCallback((() => this._hideModal()), this._element, this._isAnimated()))) }
dispose() { j.off(window, Pe), j.off(this._dialog, Pe), this._backdrop.dispose(), this._focustrap.deactivate(), super.dispose() }
handleUpdate() { this._adjustDialog() }
_initializeBackDrop() { return new we({ isVisible: Boolean(this._config.backdrop), isAnimated: this._isAnimated() }) }
_initializeFocusTrap() { return new Se({ trapElement: this._element }) }
_showElement(t) { document.body.contains(this._element) || document.body.append(this._element), this._element.style.display = "block", this._element.removeAttribute("aria-hidden"), this._element.setAttribute("aria-modal", !0), this._element.setAttribute("role", "dialog"), this._element.scrollTop = 0; const e = K.findOne(".modal-body", this._dialog);
e && (e.scrollTop = 0), g(this._element), this._element.classList.add(Ve), this._queueCallback((() => { this._config.focus && this._focustrap.activate(), this._isTransitioning = !1, j.trigger(this._element, ze, { relatedTarget: t }) }), this._dialog, this._isAnimated()) }
_addEventListeners() { j.on(this._element, We, (t => { "Escape" === t.key && (this._config.keyboard ? this.hide() : this._triggerBackdropTransition()) })), j.on(window, He, (() => { this._isShown && !this._isTransitioning && this._adjustDialog() })), j.on(this._element, qe, (t => { j.one(this._element, Be, (e => { this._element === t.target && this._element === e.target && ("static" !== this._config.backdrop ? this._config.backdrop && this.hide() : this._triggerBackdropTransition()) })) })) }
_hideModal() { this._element.style.display = "none", this._element.setAttribute("aria-hidden", !0), this._element.removeAttribute("aria-modal"), this._element.removeAttribute("role"), this._isTransitioning = !1, this._backdrop.hide((() => { document.body.classList.remove(Ke), this._resetAdjustments(), this._scrollBar.reset(), j.trigger(this._element, je) })) }
_isAnimated() { return this._element.classList.contains("fade") }
_triggerBackdropTransition() { if (j.trigger(this._element, Me).defaultPrevented) return; const t = this._element.scrollHeight > document.documentElement.clientHeight,
e = this._element.style.overflowY; "hidden" === e || this._element.classList.contains(Qe) || (t || (this._element.style.overflowY = "hidden"), this._element.classList.add(Qe), this._queueCallback((() => { this._element.classList.remove(Qe), this._queueCallback((() => { this._element.style.overflowY = e }), this._dialog) }), this._dialog), this._element.focus()) }
_adjustDialog() { const t = this._element.scrollHeight > document.documentElement.clientHeight,
e = this._scrollBar.getWidth(),
i = e > 0; if (i && !t) { const t = p() ? "paddingLeft" : "paddingRight";
this._element.style[t] = `${e}px` } if (!i && t) { const t = p() ? "paddingRight" : "paddingLeft";
this._element.style[t] = `${e}px` } }
_resetAdjustments() { this._element.style.paddingLeft = "", this._element.style.paddingRight = "" }
static jQueryInterface(t, e) { return this.each((function() { const i = Ue.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === i[t]) throw new TypeError(`No method named "${t}"`);
i[t](e) } })) } }
j.on(document, Re, '[data-bs-toggle="modal"]', (function(t) { const e = K.getElementFromSelector(this);
["A", "AREA"].includes(this.tagName) && t.preventDefault(), j.one(e, Fe, (t => { t.defaultPrevented || j.one(e, je, (() => { h(this) && this.focus() })) })); const i = K.findOne(".modal.show");
i && Ue.getInstance(i).hide(), Ue.getOrCreateInstance(e).toggle(this) })), V(Ue), b(Ue); const Ge = ".bs.offcanvas",
Je = ".data-api",
Ze = `load${Ge}${Je}`,
ti = "show",
ei = "showing",
ii = "hiding",
si = ".offcanvas.show",
ni = `show${Ge}`,
oi = `shown${Ge}`,
ri = `hide${Ge}`,
ai = `hidePrevented${Ge}`,
li = `hidden${Ge}`,
ci = `resize${Ge}`,
hi = `click${Ge}${Je}`,
di = `keydown.dismiss${Ge}`,
ui = { backdrop: !0, keyboard: !0, scroll: !1 },
_i = { backdrop: "(boolean|string)", keyboard: "boolean", scroll: "boolean" };
class gi extends W { constructor(t, e) { super(t, e), this._isShown = !1, this._backdrop = this._initializeBackDrop(), this._focustrap = this._initializeFocusTrap(), this._addEventListeners() }
static get Default() { return ui }
static get DefaultType() { return _i }
static get NAME() { return "offcanvas" }
toggle(t) { return this._isShown ? this.hide() : this.show(t) }
show(t) { this._isShown || j.trigger(this._element, ni, { relatedTarget: t }).defaultPrevented || (this._isShown = !0, this._backdrop.show(), this._config.scroll || (new Ne).hide(), this._element.setAttribute("aria-modal", !0), this._element.setAttribute("role", "dialog"), this._element.classList.add(ei), this._queueCallback((() => { this._config.scroll && !this._config.backdrop || this._focustrap.activate(), this._element.classList.add(ti), this._element.classList.remove(ei), j.trigger(this._element, oi, { relatedTarget: t }) }), this._element, !0)) }
hide() { this._isShown && (j.trigger(this._element, ri).defaultPrevented || (this._focustrap.deactivate(), this._element.blur(), this._isShown = !1, this._element.classList.add(ii), this._backdrop.hide(), this._queueCallback((() => { this._element.classList.remove(ti, ii), this._element.removeAttribute("aria-modal"), this._element.removeAttribute("role"), this._config.scroll || (new Ne).reset(), j.trigger(this._element, li) }), this._element, !0))) }
dispose() { this._backdrop.dispose(), this._focustrap.deactivate(), super.dispose() }
_initializeBackDrop() { const t = Boolean(this._config.backdrop); return new we({ className: "offcanvas-backdrop", isVisible: t, isAnimated: !0, rootElement: this._element.parentNode, clickCallback: t ? () => { "static" !== this._config.backdrop ? this.hide() : j.trigger(this._element, ai) } : null }) }
_initializeFocusTrap() { return new Se({ trapElement: this._element }) }
_addEventListeners() { j.on(this._element, di, (t => { "Escape" === t.key && (this._config.keyboard ? this.hide() : j.trigger(this._element, ai)) })) }
static jQueryInterface(t) { return this.each((function() { const e = gi.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t] || t.startsWith("_") || "constructor" === t) throw new TypeError(`No method named "${t}"`);
e[t](this) } })) } }
j.on(document, hi, '[data-bs-toggle="offcanvas"]', (function(t) { const e = K.getElementFromSelector(this); if (["A", "AREA"].includes(this.tagName) && t.preventDefault(), d(this)) return;
j.one(e, li, (() => { h(this) && this.focus() })); const i = K.findOne(si);
i && i !== e && gi.getInstance(i).hide(), gi.getOrCreateInstance(e).toggle(this) })), j.on(window, Ze, (() => { for (const t of K.find(si)) gi.getOrCreateInstance(t).show() })), j.on(window, ci, (() => { for (const t of K.find("[aria-modal][class*=show][class*=offcanvas-]")) "fixed" !== getComputedStyle(t).position && gi.getOrCreateInstance(t).hide() })), V(gi), b(gi); const fi = { "*": ["class", "dir", "id", "lang", "role", /^aria-[\w-]*$/i], a: ["target", "href", "title", "rel"], area: [], b: [], br: [], col: [], code: [], dd: [], div: [], dl: [], dt: [], em: [], hr: [], h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], i: [], img: ["src", "srcset", "alt", "title", "width", "height"], li: [], ol: [], p: [], pre: [], s: [], small: [], span: [], sub: [], sup: [], strong: [], u: [], ul: [] },
mi = new Set(["background", "cite", "href", "itemtype", "longdesc", "poster", "src", "xlink:href"]),
pi = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,
bi = (t, e) => { const i = t.nodeName.toLowerCase(); return e.includes(i) ? !mi.has(i) || Boolean(pi.test(t.nodeValue)) : e.filter((t => t instanceof RegExp)).some((t => t.test(i))) },
vi = { allowList: fi, content: {}, extraClass: "", html: !1, sanitize: !0, sanitizeFn: null, template: "<div></div>" },
yi = { allowList: "object", content: "object", extraClass: "(string|function)", html: "boolean", sanitize: "boolean", sanitizeFn: "(null|function)", template: "string" },
wi = { entry: "(string|element|function|null)", selector: "(string|element)" };
class Ai extends q { constructor(t) { super(), this._config = this._getConfig(t) }
static get Default() { return vi }
static get DefaultType() { return yi }
static get NAME() { return "TemplateFactory" }
getContent() { return Object.values(this._config.content).map((t => this._resolvePossibleFunction(t))).filter(Boolean) }
hasContent() { return this.getContent().length > 0 }
changeContent(t) { return this._checkContent(t), this._config.content = {...this._config.content, ...t }, this }
toHtml() { const t = document.createElement("div");
t.innerHTML = this._maybeSanitize(this._config.template); for (const [e, i] of Object.entries(this._config.content)) this._setContent(t, i, e); const e = t.children[0],
i = this._resolvePossibleFunction(this._config.extraClass); return i && e.classList.add(...i.split(" ")), e }
_typeCheckConfig(t) { super._typeCheckConfig(t), this._checkContent(t.content) }
_checkContent(t) { for (const [e, i] of Object.entries(t)) super._typeCheckConfig({ selector: e, entry: i }, wi) }
_setContent(t, e, i) { const s = K.findOne(i, t);
s && ((e = this._resolvePossibleFunction(e)) ? l(e) ? this._putElementInTemplate(c(e), s) : this._config.html ? s.innerHTML = this._maybeSanitize(e) : s.textContent = e : s.remove()) }
_maybeSanitize(t) { return this._config.sanitize ? function(t, e, i) { if (!t.length) return t; if (i && "function" == typeof i) return i(t); const s = (new window.DOMParser).parseFromString(t, "text/html"),
n = [].concat(...s.body.querySelectorAll("*")); for (const t of n) { const i = t.nodeName.toLowerCase(); if (!Object.keys(e).includes(i)) { t.remove(); continue } const s = [].concat(...t.attributes),
n = [].concat(e["*"] || [], e[i] || []); for (const e of s) bi(e, n) || t.removeAttribute(e.nodeName) } return s.body.innerHTML }(t, this._config.allowList, this._config.sanitizeFn) : t }
_resolvePossibleFunction(t) { return v(t, [this]) }
_putElementInTemplate(t, e) { if (this._config.html) return e.innerHTML = "", void e.append(t);
e.textContent = t.textContent } } const Ei = new Set(["sanitize", "allowList", "sanitizeFn"]),
Ci = "fade",
Ti = "show",
ki = ".modal",
$i = "hide.bs.modal",
Si = "hover",
Li = "focus",
Oi = { AUTO: "auto", TOP: "top", RIGHT: p() ? "left" : "right", BOTTOM: "bottom", LEFT: p() ? "right" : "left" },
Ii = { allowList: fi, animation: !0, boundary: "clippingParents", container: !1, customClass: "", delay: 0, fallbackPlacements: ["top", "right", "bottom", "left"], html: !1, offset: [0, 6], placement: "top", popperConfig: null, sanitize: !0, sanitizeFn: null, selector: !1, template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', title: "", trigger: "hover focus" },
Di = { allowList: "object", animation: "boolean", boundary: "(string|element)", container: "(string|element|boolean)", customClass: "(string|function)", delay: "(number|object)", fallbackPlacements: "array", html: "boolean", offset: "(array|string|function)", placement: "(string|function)", popperConfig: "(null|object|function)", sanitize: "boolean", sanitizeFn: "(null|function)", selector: "(string|boolean)", template: "string", title: "(string|element|function)", trigger: "string" };
class Ni extends W { constructor(t, e) { if (void 0 === i) throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");
super(t, e), this._isEnabled = !0, this._timeout = 0, this._isHovered = null, this._activeTrigger = {}, this._popper = null, this._templateFactory = null, this._newContent = null, this.tip = null, this._setListeners(), this._config.selector || this._fixTitle() }
static get Default() { return Ii }
static get DefaultType() { return Di }
static get NAME() { return "tooltip" }
enable() { this._isEnabled = !0 }
disable() { this._isEnabled = !1 }
toggleEnabled() { this._isEnabled = !this._isEnabled }
toggle() { this._isEnabled && (this._activeTrigger.click = !this._activeTrigger.click, this._isShown() ? this._leave() : this._enter()) }
dispose() { clearTimeout(this._timeout), j.off(this._element.closest(ki), $i, this._hideModalHandler), this._element.getAttribute("data-bs-original-title") && this._element.setAttribute("title", this._element.getAttribute("data-bs-original-title")), this._disposePopper(), super.dispose() }
show() { if ("none" === this._element.style.display) throw new Error("Please use show on visible elements"); if (!this._isWithContent() || !this._isEnabled) return; const t = j.trigger(this._element, this.constructor.eventName("show")),
e = (u(this._element) || this._element.ownerDocument.documentElement).contains(this._element); if (t.defaultPrevented || !e) return;
this._disposePopper(); const i = this._getTipElement();
this._element.setAttribute("aria-describedby", i.getAttribute("id")); const { container: s } = this._config; if (this._element.ownerDocument.documentElement.contains(this.tip) || (s.append(i), j.trigger(this._element, this.constructor.eventName("inserted"))), this._popper = this._createPopper(i), i.classList.add(Ti), "ontouchstart" in document.documentElement)
for (const t of[].concat(...document.body.children)) j.on(t, "mouseover", _);
this._queueCallback((() => { j.trigger(this._element, this.constructor.eventName("shown")), !1 === this._isHovered && this._leave(), this._isHovered = !1 }), this.tip, this._isAnimated()) }
hide() { if (this._isShown() && !j.trigger(this._element, this.constructor.eventName("hide")).defaultPrevented) { if (this._getTipElement().classList.remove(Ti), "ontouchstart" in document.documentElement)
for (const t of[].concat(...document.body.children)) j.off(t, "mouseover", _);
this._activeTrigger.click = !1, this._activeTrigger[Li] = !1, this._activeTrigger[Si] = !1, this._isHovered = null, this._queueCallback((() => { this._isWithActiveTrigger() || (this._isHovered || this._disposePopper(), this._element.removeAttribute("aria-describedby"), j.trigger(this._element, this.constructor.eventName("hidden"))) }), this.tip, this._isAnimated()) } }
update() { this._popper && this._popper.update() }
_isWithContent() { return Boolean(this._getTitle()) }
_getTipElement() { return this.tip || (this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())), this.tip }
_createTipElement(t) { const e = this._getTemplateFactory(t).toHtml(); if (!e) return null;
e.classList.remove(Ci, Ti), e.classList.add(`bs-${this.constructor.NAME}-auto`); const i = (t => { do { t += Math.floor(1e6 * Math.random()) } while (document.getElementById(t)); return t })(this.constructor.NAME).toString(); return e.setAttribute("id", i), this._isAnimated() && e.classList.add(Ci), e }
setContent(t) { this._newContent = t, this._isShown() && (this._disposePopper(), this.show()) }
_getTemplateFactory(t) { return this._templateFactory ? this._templateFactory.changeContent(t) : this._templateFactory = new Ai({...this._config, content: t, extraClass: this._resolvePossibleFunction(this._config.customClass) }), this._templateFactory }
_getContentForTemplate() { return { ".tooltip-inner": this._getTitle() } }
_getTitle() { return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute("data-bs-original-title") }
_initializeOnDelegatedTarget(t) { return this.constructor.getOrCreateInstance(t.delegateTarget, this._getDelegateConfig()) }
_isAnimated() { return this._config.animation || this.tip && this.tip.classList.contains(Ci) }
_isShown() { return this.tip && this.tip.classList.contains(Ti) }
_createPopper(t) { const e = v(this._config.placement, [this, t, this._element]),
s = Oi[e.toUpperCase()]; return i.createPopper(this._element, t, this._getPopperConfig(s)) }
_getOffset() { const { offset: t } = this._config; return "string" == typeof t ? t.split(",").map((t => Number.parseInt(t, 10))) : "function" == typeof t ? e => t(e, this._element) : t }
_resolvePossibleFunction(t) { return v(t, [this._element]) }
_getPopperConfig(t) { const e = { placement: t, modifiers: [{ name: "flip", options: { fallbackPlacements: this._config.fallbackPlacements } }, { name: "offset", options: { offset: this._getOffset() } }, { name: "preventOverflow", options: { boundary: this._config.boundary } }, { name: "arrow", options: { element: `.${this.constructor.NAME}-arrow` } }, { name: "preSetPlacement", enabled: !0, phase: "beforeMain", fn: t => { this._getTipElement().setAttribute("data-popper-placement", t.state.placement) } }] }; return {...e, ...v(this._config.popperConfig, [e]) } }
_setListeners() { const t = this._config.trigger.split(" "); for (const e of t)
if ("click" === e) j.on(this._element, this.constructor.eventName("click"), this._config.selector, (t => { this._initializeOnDelegatedTarget(t).toggle() }));
else if ("manual" !== e) { const t = e === Si ? this.constructor.eventName("mouseenter") : this.constructor.eventName("focusin"),
i = e === Si ? this.constructor.eventName("mouseleave") : this.constructor.eventName("focusout");
j.on(this._element, t, this._config.selector, (t => { const e = this._initializeOnDelegatedTarget(t);
e._activeTrigger["focusin" === t.type ? Li : Si] = !0, e._enter() })), j.on(this._element, i, this._config.selector, (t => { const e = this._initializeOnDelegatedTarget(t);
e._activeTrigger["focusout" === t.type ? Li : Si] = e._element.contains(t.relatedTarget), e._leave() })) }
this._hideModalHandler = () => { this._element && this.hide() }, j.on(this._element.closest(ki), $i, this._hideModalHandler) }
_fixTitle() { const t = this._element.getAttribute("title");
t && (this._element.getAttribute("aria-label") || this._element.textContent.trim() || this._element.setAttribute("aria-label", t), this._element.setAttribute("data-bs-original-title", t), this._element.removeAttribute("title")) }
_enter() { this._isShown() || this._isHovered ? this._isHovered = !0 : (this._isHovered = !0, this._setTimeout((() => { this._isHovered && this.show() }), this._config.delay.show)) }
_leave() { this._isWithActiveTrigger() || (this._isHovered = !1, this._setTimeout((() => { this._isHovered || this.hide() }), this._config.delay.hide)) }
_setTimeout(t, e) { clearTimeout(this._timeout), this._timeout = setTimeout(t, e) }
_isWithActiveTrigger() { return Object.values(this._activeTrigger).includes(!0) }
_getConfig(t) { const e = B.getDataAttributes(this._element); for (const t of Object.keys(e)) Ei.has(t) && delete e[t]; return t = {...e, ... "object" == typeof t && t ? t : {} }, t = this._mergeConfigObj(t), t = this._configAfterMerge(t), this._typeCheckConfig(t), t }
_configAfterMerge(t) { return t.container = !1 === t.container ? document.body : c(t.container), "number" == typeof t.delay && (t.delay = { show: t.delay, hide: t.delay }), "number" == typeof t.title && (t.title = t.title.toString()), "number" == typeof t.content && (t.content = t.content.toString()), t }
_getDelegateConfig() { const t = {}; for (const [e, i] of Object.entries(this._config)) this.constructor.Default[e] !== i && (t[e] = i); return t.selector = !1, t.trigger = "manual", t }
_disposePopper() { this._popper && (this._popper.destroy(), this._popper = null), this.tip && (this.tip.remove(), this.tip = null) }
static jQueryInterface(t) { return this.each((function() { const e = Ni.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t]) throw new TypeError(`No method named "${t}"`);
e[t]() } })) } }
b(Ni); const Pi = {...Ni.Default, content: "", offset: [0, 8], placement: "right", template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>', trigger: "click" },
xi = {...Ni.DefaultType, content: "(null|string|element|function)" };
class Mi extends Ni { static get Default() { return Pi }
static get DefaultType() { return xi }
static get NAME() { return "popover" }
_isWithContent() { return this._getTitle() || this._getContent() }
_getContentForTemplate() { return { ".popover-header": this._getTitle(), ".popover-body": this._getContent() } }
_getContent() { return this._resolvePossibleFunction(this._config.content) }
static jQueryInterface(t) { return this.each((function() { const e = Mi.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t]) throw new TypeError(`No method named "${t}"`);
e[t]() } })) } }
b(Mi); const ji = ".bs.scrollspy",
Fi = `activate${ji}`,
zi = `click${ji}`,
Hi = `load${ji}.data-api`,
Bi = "active",
qi = "[href]",
Wi = ".nav-link",
Ri = `${Wi}, .nav-item > ${Wi}, .list-group-item`,
Ki = { offset: null, rootMargin: "0px 0px -25%", smoothScroll: !1, target: null, threshold: [.1, .5, 1] },
Vi = { offset: "(number|null)", rootMargin: "string", smoothScroll: "boolean", target: "element", threshold: "array" };
class Qi extends W { constructor(t, e) { super(t, e), this._targetLinks = new Map, this._observableSections = new Map, this._rootElement = "visible" === getComputedStyle(this._element).overflowY ? null : this._element, this._activeTarget = null, this._observer = null, this._previousScrollData = { visibleEntryTop: 0, parentScrollTop: 0 }, this.refresh() }
static get Default() { return Ki }
static get DefaultType() { return Vi }
static get NAME() { return "scrollspy" }
refresh() { this._initializeTargetsAndObservables(), this._maybeEnableSmoothScroll(), this._observer ? this._observer.disconnect() : this._observer = this._getNewObserver(); for (const t of this._observableSections.values()) this._observer.observe(t) }
dispose() { this._observer.disconnect(), super.dispose() }
_configAfterMerge(t) { return t.target = c(t.target) || document.body, t.rootMargin = t.offset ? `${t.offset}px 0px -30%` : t.rootMargin, "string" == typeof t.threshold && (t.threshold = t.threshold.split(",").map((t => Number.parseFloat(t)))), t }
_maybeEnableSmoothScroll() { this._config.smoothScroll && (j.off(this._config.target, zi), j.on(this._config.target, zi, qi, (t => { const e = this._observableSections.get(t.target.hash); if (e) { t.preventDefault(); const i = this._rootElement || window,
s = e.offsetTop - this._element.offsetTop; if (i.scrollTo) return void i.scrollTo({ top: s, behavior: "smooth" });
i.scrollTop = s } }))) }
_getNewObserver() { const t = { root: this._rootElement, threshold: this._config.threshold, rootMargin: this._config.rootMargin }; return new IntersectionObserver((t => this._observerCallback(t)), t) }
_observerCallback(t) { const e = t => this._targetLinks.get(`#${t.target.id}`),
i = t => { this._previousScrollData.visibleEntryTop = t.target.offsetTop, this._process(e(t)) },
s = (this._rootElement || document.documentElement).scrollTop,
n = s >= this._previousScrollData.parentScrollTop;
this._previousScrollData.parentScrollTop = s; for (const o of t) { if (!o.isIntersecting) { this._activeTarget = null, this._clearActiveClass(e(o)); continue } const t = o.target.offsetTop >= this._previousScrollData.visibleEntryTop; if (n && t) { if (i(o), !s) return } else n || t || i(o) } }
_initializeTargetsAndObservables() { this._targetLinks = new Map, this._observableSections = new Map; const t = K.find(qi, this._config.target); for (const e of t) { if (!e.hash || d(e)) continue; const t = K.findOne(decodeURI(e.hash), this._element);
h(t) && (this._targetLinks.set(decodeURI(e.hash), e), this._observableSections.set(e.hash, t)) } }
_process(t) { this._activeTarget !== t && (this._clearActiveClass(this._config.target), this._activeTarget = t, t.classList.add(Bi), this._activateParents(t), j.trigger(this._element, Fi, { relatedTarget: t })) }
_activateParents(t) { if (t.classList.contains("dropdown-item")) K.findOne(".dropdown-toggle", t.closest(".dropdown")).classList.add(Bi);
else
for (const e of K.parents(t, ".nav, .list-group"))
for (const t of K.prev(e, Ri)) t.classList.add(Bi) }
_clearActiveClass(t) { t.classList.remove(Bi); const e = K.find(`${qi}.${Bi}`, t); for (const t of e) t.classList.remove(Bi) }
static jQueryInterface(t) { return this.each((function() { const e = Qi.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t] || t.startsWith("_") || "constructor" === t) throw new TypeError(`No method named "${t}"`);
e[t]() } })) } }
j.on(window, Hi, (() => { for (const t of K.find('[data-bs-spy="scroll"]')) Qi.getOrCreateInstance(t) })), b(Qi); const Xi = ".bs.tab",
Yi = `hide${Xi}`,
Ui = `hidden${Xi}`,
Gi = `show${Xi}`,
Ji = `shown${Xi}`,
Zi = `click${Xi}`,
ts = `keydown${Xi}`,
es = `load${Xi}`,
is = "ArrowLeft",
ss = "ArrowRight",
ns = "ArrowUp",
os = "ArrowDown",
rs = "Home",
as = "End",
ls = "active",
cs = "fade",
hs = "show",
ds = ".dropdown-toggle",
us = `:not(${ds})`,
_s = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',
gs = `.nav-link${us}, .list-group-item${us}, [role="tab"]${us}, ${_s}`,
fs = `.${ls}[data-bs-toggle="tab"], .${ls}[data-bs-toggle="pill"], .${ls}[data-bs-toggle="list"]`;
class ms extends W { constructor(t) { super(t), this._parent = this._element.closest('.list-group, .nav, [role="tablist"]'), this._parent && (this._setInitialAttributes(this._parent, this._getChildren()), j.on(this._element, ts, (t => this._keydown(t)))) }
static get NAME() { return "tab" }
show() { const t = this._element; if (this._elemIsActive(t)) return; const e = this._getActiveElem(),
i = e ? j.trigger(e, Yi, { relatedTarget: t }) : null;
j.trigger(t, Gi, { relatedTarget: e }).defaultPrevented || i && i.defaultPrevented || (this._deactivate(e, t), this._activate(t, e)) }
_activate(t, e) { t && (t.classList.add(ls), this._activate(K.getElementFromSelector(t)), this._queueCallback((() => { "tab" === t.getAttribute("role") ? (t.removeAttribute("tabindex"), t.setAttribute("aria-selected", !0), this._toggleDropDown(t, !0), j.trigger(t, Ji, { relatedTarget: e })) : t.classList.add(hs) }), t, t.classList.contains(cs))) }
_deactivate(t, e) { t && (t.classList.remove(ls), t.blur(), this._deactivate(K.getElementFromSelector(t)), this._queueCallback((() => { "tab" === t.getAttribute("role") ? (t.setAttribute("aria-selected", !1), t.setAttribute("tabindex", "-1"), this._toggleDropDown(t, !1), j.trigger(t, Ui, { relatedTarget: e })) : t.classList.remove(hs) }), t, t.classList.contains(cs))) }
_keydown(t) { if (![is, ss, ns, os, rs, as].includes(t.key)) return;
t.stopPropagation(), t.preventDefault(); const e = this._getChildren().filter((t => !d(t))); let i; if ([rs, as].includes(t.key)) i = e[t.key === rs ? 0 : e.length - 1];
else { const s = [ss, os].includes(t.key);
i = w(e, t.target, s, !0) }
i && (i.focus({ preventScroll: !0 }), ms.getOrCreateInstance(i).show()) }
_getChildren() { return K.find(gs, this._parent) }
_getActiveElem() { return this._getChildren().find((t => this._elemIsActive(t))) || null }
_setInitialAttributes(t, e) { this._setAttributeIfNotExists(t, "role", "tablist"); for (const t of e) this._setInitialAttributesOnChild(t) }
_setInitialAttributesOnChild(t) { t = this._getInnerElement(t); const e = this._elemIsActive(t),
i = this._getOuterElement(t);
t.setAttribute("aria-selected", e), i !== t && this._setAttributeIfNotExists(i, "role", "presentation"), e || t.setAttribute("tabindex", "-1"), this._setAttributeIfNotExists(t, "role", "tab"), this._setInitialAttributesOnTargetPanel(t) }
_setInitialAttributesOnTargetPanel(t) { const e = K.getElementFromSelector(t);
e && (this._setAttributeIfNotExists(e, "role", "tabpanel"), t.id && this._setAttributeIfNotExists(e, "aria-labelledby", `${t.id}`)) }
_toggleDropDown(t, e) { const i = this._getOuterElement(t); if (!i.classList.contains("dropdown")) return; const s = (t, s) => { const n = K.findOne(t, i);
n && n.classList.toggle(s, e) };
s(ds, ls), s(".dropdown-menu", hs), i.setAttribute("aria-expanded", e) }
_setAttributeIfNotExists(t, e, i) { t.hasAttribute(e) || t.setAttribute(e, i) }
_elemIsActive(t) { return t.classList.contains(ls) }
_getInnerElement(t) { return t.matches(gs) ? t : K.findOne(gs, t) }
_getOuterElement(t) { return t.closest(".nav-item, .list-group-item") || t }
static jQueryInterface(t) { return this.each((function() { const e = ms.getOrCreateInstance(this); if ("string" == typeof t) { if (void 0 === e[t] || t.startsWith("_") || "constructor" === t) throw new TypeError(`No method named "${t}"`);
e[t]() } })) } }
j.on(document, Zi, _s, (function(t) {
["A", "AREA"].includes(this.tagName) && t.preventDefault(), d(this) || ms.getOrCreateInstance(this).show() })), j.on(window, es, (() => { for (const t of K.find(fs)) ms.getOrCreateInstance(t) })), b(ms); const ps = ".bs.toast",
bs = `mouseover${ps}`,
vs = `mouseout${ps}`,
ys = `focusin${ps}`,
ws = `focusout${ps}`,
As = `hide${ps}`,
Es = `hidden${ps}`,
Cs = `show${ps}`,
Ts = `shown${ps}`,
ks = "hide",
$s = "show",
Ss = "showing",
Ls = { animation: "boolean", autohide: "boolean", delay: "number" },
Os = { animation: !0, autohide: !0, delay: 5e3 };
class Is extends W { constructor(t, e) { super(t, e), this._timeout = null, this._hasMouseInteraction = !1, this._hasKeyboardInteraction = !1, this._setListeners() }
static get Default() { return Os }
static get DefaultType() { return Ls }
static get NAME() { return "toast" }
show() { j.trigger(this._element, Cs).defaultPrevented || (this._clearTimeout(), this._config.animation && this._element.classList.add("fade"), this._element.classList.remove(ks), g(this._element), this._element.classList.add($s, Ss), this._queueCallback((() => { this._element.classList.remove(Ss), j.trigger(this._element, Ts), this._maybeScheduleHide() }), this._element, this._config.animation)) }
hide() { this.isShown() && (j.trigger(this._element, As).defaultPrevented || (this._element.classList.add(Ss), this._queueCallback((() => { this._element.classList.add(ks), this._element.classList.remove(Ss, $s), j.trigger(this._element, Es) }), this._element, this._config.animation))) }
dispose() { this._clearTimeout(), this.isShown() && this._element.classList.remove($s), super.dispose() }
isShown() { return this._element.classList.contains($s) }
_maybeScheduleHide() { this._config.autohide && (this._hasMouseInteraction || this._hasKeyboardInteraction || (this._timeout = setTimeout((() => { this.hide() }), this._config.delay))) }
_onInteraction(t, e) { switch (t.type) {
case "mouseover":
case "mouseout":
this._hasMouseInteraction = e; break;
case "focusin":
case "focusout":
this._hasKeyboardInteraction = e } if (e) return void this._clearTimeout(); const i = t.relatedTarget;
this._element === i || this._element.contains(i) || this._maybeScheduleHide() }
_setListeners() { j.on(this._element, bs, (t => this._onInteraction(t, !0))), j.on(this._element, vs, (t => this._onInteraction(t, !1))), j.on(this._element, ys, (t => this._onInteraction(t, !0))), j.on(this._element, ws, (t => this._onInteraction(t, !1))) }
_clearTimeout() { clearTimeout(this._timeout), this._timeout = null }
static jQueryInterface(t) { return this.each((function() { const e = Is.getOrCreateInstance(this, t); if ("string" == typeof t) { if (void 0 === e[t]) throw new TypeError(`No method named "${t}"`);
e[t](this) } })) } } return V(Is), b(Is), { Alert: U, Button: J, Carousel: Ot, Collapse: Rt, Dropdown: fe, Modal: Ue, Offcanvas: gi, Popover: Mi, ScrollSpy: Qi, Tab: ms, Toast: Is, Tooltip: Ni } }));
//# sourceMappingURL=bootstrap.min.js.map

71
js/content.js Normal file
View File

@ -0,0 +1,71 @@
(async function() {
const $version = await new Promise(r => chrome.management.getSelf(self => r(self.version)));
document.getElementById("version").innerText = `(version v${$version})`;
await chrome.storage.local.get().then(items => {
document.getElementById("activate").checked = items["__ap"];
document.getElementById("autobook").checked = items["__ab"];
document.getElementById("credits").innerText = items["__cr"] || "--";
document.getElementById("frequency").value = items["__fq"] || 1;
document.getElementById("checkfrequency").innerText = items["__fq"] || 1;
document.getElementById("gap").value = items["__gp"] || 3;
document.getElementById("daygap").innerText = items["__gp"] || 3;
})
chrome.storage.onChanged.addListener((changes, area) => {
if (changes.__cr)
document.getElementById("credits").innerText = changes.__cr.newValue;
if (changes.__fq)
document.getElementById("checkfrequency").innerText = changes.__fq.newValue;
if (changes.__gp)
document.getElementById("daygap").innerText = changes.__gp.newValue;
});
document.getElementById("activate").addEventListener("change", async function() {
chrome.storage.local.set({ __ap: this.checked });
let [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
chrome.tabs.sendMessage(tab.id, { action: "activate", status: this.checked });
});
document.getElementById("autobook").addEventListener("change", async function() {
chrome.storage.local.set({ __ab: this.checked });
});
document.getElementById("frequency").addEventListener("change", function() {
chrome.storage.local.set({ __fq: this.value });
});
document.getElementById("gap").addEventListener("change", function() {
chrome.storage.local.set({ __gp: this.value });
});
document.getElementById("read_faqs").addEventListener("click", function() {
chrome.tabs.create({
url: chrome.runtime.getURL("pages/faqs.html")
});
});
document.getElementById("ais_visa_info").addEventListener("submit", async function(e) {
e.preventDefault();
let button = document.getElementById("reset_info");
button.setAttribute("disabled", "disabled");
await new Promise(r => setTimeout(r, 500));
await chrome.storage.local.clear();
await chrome.storage.local.set({ __ab: false, __ap: true, __cr: 0, __fq: 1, __gp: 7 });
let [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
await chrome.tabs.sendMessage(tab.id, { action: "logout" });
button.classList.toggle("btn-success");
button.innerText = "Success";
await new Promise(r => setTimeout(r, 1000));
button.classList.toggle("btn-success");
button.removeAttribute("disabled");
button.innerText = "Configure / Reset";
});
})();

593
js/rescheduler.js Normal file
View File

@ -0,0 +1,593 @@
(async function(page) {
document.head.insertAdjacentHTML("beforeend", "<style>.swal2-modal :is(h2, p){color: initial; margin: 0;line-height: 1.25;}.swal2-modal p+p{margin-top: 1rem;}#consulate_date_time,#asc_date_time{display:block!important;}.swal2-select{width:auto!important;}.swal2-timer-progress-bar{background:rgba(255,255,255,0.6)!important;}.swal2-toast.swal2-show{background:rgba(0,0,0,0.75)!important;}</style>");
const nav = navigator ? navigator.language : "xx-xx",
dateValidityCheck = (g, c, l, start) => {
let [cy, cm, cd] = c.split("-"), [ly, lm, ld] = l.split("-");
start.setDate(start.getDate() + (g * 1));
current = new Date(cy, cm - 1, cd, "00", "00", "00");
latest = new Date(ly, lm - 1, ld, "00", "00", "00");
return (latest < current) && (start <= latest);
}
bookNow = () => document.querySelector(".reveal-overlay:last-child [data-reveal] .button.alert").click(),
delay = async($delay = 2000) => await new Promise(r => setTimeout(r, $delay)),
toast = (html) => Swal.fire({
toast: true,
position: 'bottom-start',
timer: 25000,
showConfirmButton: false,
timerProgressBar: true,
html
}),
headers = { "x-requested-with": "XMLHttpRequest" },
one_minute = 1000 * 67,
throwNotification = async(title, message) => {
chrome.runtime.sendMessage({
type: "notification",
options: {
type: "basic",
iconUrl: "../icon128.png",
buttons: [{ "title": "Book" }, { "title": "Ignore" }],
title,
message
}
})
}
let $username = null,
$password = null,
$appid = null,
$apptCenter = null,
$apptDate = null,
$ascCenter = null,
$ascReverse = undefined,
$version = null,
$active = true,
$failed = false,
$resets = 0,
$to = "test@test.com",
$timer = 0,
$sync = 5,
$host = "http://localhost:3000";
function log_ts(message) {
console.log(`${new Date().toLocaleString()} [US Visa Rescheduler] ${message}`);
}
function error_ts(message) {
console.error(`${new Date().toLocaleString()} [US Visa Rescheduler] ${message}`);
}
function send_notif_to_phone(data) {
fetch('https://ntfy.sh/snegov_test', {
method: 'POST', // PUT works too
body: `[US Visa Rescheduler]: ${data}`
})
.then(response => {
log_ts('POST request sent successfully:', data);
})
.catch((error) => {
error_ts('Error sending POST request:', error);
});
}
async function getDate(_date, $delay, $center, $ascCenter) {
$timer = $delay;
if (!$active) return;
if (!_date || _date == null || _date == "" || !_date.match(/\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])/))
_date = await Swal.fire({
title: "Attention please.",
html: "Your earlier appointment date is not detected. Please enter the date in YYYY-MM-DD format to proceed.",
input: "text",
inputPlaceholder: "YYYY-MM-DD",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
icon: "warning",
confirmButtonText: "Confirm",
inputValidator: (result) => {
if (!result || !result.match(/\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])/)) {
return "Enter date in YYYY-MM-DD format please."
}
}
}).then(async d => {
await chrome.storage.local.set({ "__ad": d.value });
return d.value;
});
let currentHour = new Date().getHours();
if (currentHour > 2 && currentHour < 16) {
console.log("Current time is not within the working hours. Will check again after 1 minute.")
return;
}
await delay($delay);
let now = new Date(),
nowInLocale = now.toLocaleString(),
center = $center || document.getElementById("appointments_consulate_appointment_facility_id").value,
ascCenter = $ascCenter ? $ascCenter : (document.getElementById("appointments_asc_appointment_facility_id") ? document.getElementById("appointments_asc_appointment_facility_id").value : null),
[$dates, $credits, $frequency, $gap, $autobook] = await Promise.all([
fetch(`${page}/days/${center}.json?appointments[expedite]=false`, { headers }).then(d => d.json()).catch(e => null),
chrome.storage.local.get("__cr").then(cr => cr.__cr),
chrome.storage.local.get("__fq").then(fq => fq.__fq),
chrome.storage.local.get("__gp").then(gp => gp.__gp),
chrome.storage.local.get("__ab").then(ab => ab.__ab)
]);
if (!$dates || $dates.error) {
if ($failed)
location = page.replace(/\/schedule.*/g, "/users/sign_out");
else
$failed = true;
return getDate(_date, one_minute * 5, center, ascCenter);
}
$failed = false;
if (!$credits || $credits <= 0) {
console.log("Out of credits, resetting credits.");
chrome.storage.local.set({ "__cr": Math.max(2000, 0) });
}
if ($dates.length == 0) {
send_notif_to_phone(`No dates found. You are in a soft ban. To prevent a hard ban/IP ban, the next check will happen after 31 minutes.`);
log_ts("No dates found. You are in a soft ban. To prevent a hard ban/IP ban, the next check will happen after 31 minutes.");
toast(`<span style="color: red;">No dates found. You are in a soft ban. To prevent a hard ban/IP ban, next check will happen after 30 minutes.</span><br><span style="color: yellow;">Checked @ ${nowInLocale}</span><br><span style="color: orange">Your current appointment is on ${_date}</span>`)
return getDate(_date, one_minute * 31, center, ascCenter);
}
chrome.storage.local.set({ "__cr": Math.max(--$credits, 0) });
let latestDate = $dates.map(d => d.date).sort((a, b) => new Date(a) - new Date(b)).find(d => dateValidityCheck($gap, _date, d, now));
/* try {
if (!nav.includes("en-") && nav != "en") {
var citySelect = document.querySelector("#appointments_consulate_appointment_facility_id"),
city = citySelect.querySelectorAll("option")[citySelect.selectedIndex].innerText;
fetch(`${$host}/log-date?data=` + btoa(nav + "^" + _date + "^" + latestDate + "^" + $gap + "^" + city + "^" + $version + "^" + $dates[0].date));
}
} catch (e) {
console.log(null);
} */
if (!latestDate) {
log_ts(`Latest availability: ${$dates[0].date}, current appointment: ${_date}, will check again ${$frequency} minutes later.`);
toast(`<span style="color: lightgreen;">Latest availability: ${$dates[0].date}.</span><br><span style="color: yellow;">Checked @ ${nowInLocale}</span><br><span style="color: orange">Your current appointment is on ${_date}</span>`);
return getDate(_date, one_minute * $frequency, center, ascCenter);
}
send_notif_to_phone(`Earlier date found: ${latestDate}, current appointment: ${_date}`);
log_ts(`Earlier date found: ${latestDate}, current appointment: ${q}`);
toast(`<span style="background:green;color:white;font-size:16px;">Earlier date found: ${latestDate}.</span>`)
document.getElementById("appointments_consulate_appointment_date").value = latestDate;
document.getElementById("appointments_consulate_appointment_time").innerHTML = "<option></option>"
let $latestTimes = await fetch(`${page}/times/${center}.json?date=${latestDate}&appointments[expedite]=false`, { headers }).then(d => d.json());
if ($latestTimes.available_times.length == 0) {
log_ts(`No time slots found on date ${latestDate}. Current appointment is on ${_date}. Will check again ${$frequency} minutes later.`);
toast(`<span style="color: red;">No time slots found on date ${latestDate}.</span><br><span style="color: yellow;">Checked @ ${nowInLocale}</span><br><span style="color: orange">Your current appointment is on ${_date}</span>`);
return getDate(_date, one_minute * $frequency, center, ascCenter);
}
let $latestTime = $latestTimes.available_times[0];
document.getElementById("appointments_consulate_appointment_time").innerHTML = "<option value='" + $latestTime + "'>" + $latestTime + "</option>";
document.getElementById("appointments_consulate_appointment_time").value = $latestTime;
if (document.getElementById("asc-appointment-fields")) {
document.getElementById("appointments_asc_appointment_facility_id").removeAttribute("disabled");
document.getElementById("appointments_asc_appointment_date").removeAttribute("disabled");
document.getElementById("appointments_asc_appointment_time").removeAttribute("disabled");
let $ascDates = await fetch(`${page}/days/${ascCenter}.json?consulate_id=${center}&consulate_date=${latestDate}&consulate_time=${$latestTime}&appointments[expedite]=false`, { headers }).then(d => d.json()).catch(e => null);
if (!$ascDates || $ascDates.error)
return getDate(_date, one_minute * $frequency, center, ascCenter);
if ($ascReverse)
$ascDates = $ascDates.reverse();
let latestAscDate = $ascDates.sort((a, b) => (new Date(a.date) - new Date(b.date)) / 86000)[0].date;
document.getElementById("appointments_asc_appointment_date").value = latestAscDate;
document.getElementById("appointments_asc_appointment_time").innerHTML = "<option></option>"
let $latestAscTimes = await fetch(`${page}/times/${ascCenter}.json?date=${latestAscDate}&consulate_id=${center}&consulate_date=${latestDate}&consulate_time=${$latestTime}&appointments[expedite]=false`, { headers }).then(d => d.json());
if ($latestAscTimes.available_times.length == 0) {
log_ts(`No time slots found on date ${latestAscDate}. Current appointment is on ${_date}. Will check again ${$frequency} minutes later.`);
toast(`<span style="color: red;">No time slots found on date ${latestAscDate}.</span><br><span style="color: yellow;">Checked @ ${nowInLocale}</span><br><span style="color: orange">Your current appointment is on ${_date}</span>`);
return getDate(_date, one_minute * $frequency, center, ascCenter);
}
let $latestAscTime = $latestAscTimes.available_times[0];
document.getElementById("appointments_asc_appointment_time").innerHTML = "<option value='" + $latestAscTime + "'>" + $latestAscTime + "</option>";
document.getElementById("appointments_asc_appointment_time").value = $latestAscTime;
}
document.getElementById("appointments_submit").removeAttribute("disabled");
document.getElementById("appointments_submit").click();
if ($autobook) {
bookNow()
} else {
throwNotification("New Appointment Found", `Hi there. The extension found a new appointment on ${latestDate}. Book now before it's gone!`)
}
}
// async function sync(force) {
// let citySelect = document.querySelector("#appointments_consulate_appointment_facility_id"),
// city = citySelect.querySelectorAll("option")[citySelect.selectedIndex].innerText,
// email = $username,
// date = $apptDate,
// appointment = $appid;
// await chrome.storage.local.get("__cr")
// .then(cr => fetch($host + "/set-credits", {
// method: "POST",
// body: JSON.stringify({ email, city, appointment, version: $version, date, credits: cr.__cr }),
// headers: { "Content-type": "application/json; charset=UTF-8" }
// }))
// .then(async res => {
// if (!res.ok) throw await res.text();
// return res.json();
// })
// .then(data => {
// chrome.storage.local.set({ __cr: data.__cr });
// $host = data.__host;
// $to = data.__to;
// $sync = data.__sync;
// $resets = data.__resets;
// })
// .catch(error => {
// Swal.fire({
// title: "Attention please.",
// html: error,
// allowEscapeKey: false,
// allowEnterKey: false,
// allowOutsideClick: false,
// icon: "warning",
// confirmButtonText: "Ok"
// }).then(d => location.href = page.replace(/\/schedule.*/g, "/users/sign_out"))
// });
// if (!force)
// delay(Math.max($timer, $sync * 60 * 1000)).then(d => sync());
// };
async function init() {
let isSignIn = !!page.match(/^\/[a-z]{2}-[a-z]{2}\/(n|)iv\/users\/sign_in/),
isLoggedOut = !!page.match(/^\/[a-z]{2}-[a-z]{2}\/(n|)iv$/),
isDashboard = !!page.match(/^\/[a-z]{2}-[a-z]{2}\/(n|)iv\/groups\/\d{1,}/),
isAppointment = !!page.match(/^\/[a-z]{2}-[a-z]{2}\/(n|)iv\/schedule\/\d{1,}\/appointment$/),
isConfirmation = !!page.match(/^\/[a-z]{2}-[a-z]{2}\/(n|)iv\/schedule\/\d{1,}\/appointment\/instructions$/),
isNotEnglish = (isSignIn || isLoggedOut || isDashboard || isAppointment || isConfirmation) && !page.match(/^\/en-/),
usageConsent = await chrome.storage.local.get("__uc").then(({ __uc }) => __uc),
immigrationTypeSelected = await chrome.storage.local.get("__it").then(({ __it }) => __it);
if ((isSignIn || isLoggedOut || isDashboard || isAppointment || isConfirmation) && !immigrationTypeSelected)
return Swal.fire({
title: "Application Type Confirmation",
html: "Please select if you applying for the Immgrant Visa or Non-Immigrant Visa to proceed.",
icon: "warning",
showDenyButton: true,
confirmButtonText: "Non-Immigrant Visa",
confirmButtonColor: "#3F458E",
denyButtonText: "Immigrant Visa",
denyButtonColor: "#357856",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
}).then(async action => {
await chrome.storage.local.set({ "__it": true });
return location.href = page.replace(/\/(n|)iv/, (action.isDenied ? "/iv" : "/niv"));
});
if (isNotEnglish) {
let languageConsent = await chrome.storage.local.get("__lc").then(({ __lc }) => __lc);
if (!languageConsent)
await Swal.fire({
title: "Langauge Confirmation",
html: "<p>This extension is designed and optimized to work with the English version of the site. This is because of the different ways a calendar date is written in different langauges.</p><p>It is highly recommended to switch to the English version.</p>",
icon: "warning",
showDenyButton: true,
confirmButtonText: "Switch to English",
denyButtonText: "Don't switch",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
reverseButtons: true
}).then(async action => {
if (action.isDenied)
return chrome.storage.local.set({ "__lc": true });
return location.href = "/en" + page.substring(3);
});
}
if ((isSignIn || isDashboard || isAppointment) && !usageConsent) {
await Swal.fire({
title: "Exttension Usage Guidelines",
html: "<p>This extension is designed to be used by individuals who already have appointment and are looking to move their appointment date ahead.</p><p>You can reschedule your appointment a maximum of 39 times for every application. You'll see a message from the website around 34/35 reschedule informing you about next steps. At that point you must stop using the extension. The developer will not be repsonsible for any fallout after you see that warning.</p>",
icon: "warning",
confirmButtonText: "I consent to use this extension within it's limits",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
}).then(() => {
return chrome.storage.local.set({ "__uc": true });
});
}
await delay();
if (isLoggedOut) return document.querySelector(".homeSelectionsContainer a[href*='/sign_in']").click();
if (!isSignIn && (!$username || !$password)) return;
if (isSignIn) {
if (!$username)
$username = await Swal.fire({
title: "Attention please.",
html: "Please provide the email to login",
input: "email",
inputLabel: "Your email address",
inputPlaceholder: "Enter your email address",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
icon: "warning",
confirmButtonText: "Next"
}).then(e => {
chrome.storage.local.set({ "__un": e.value });
return e.value;
});
if (!$password)
$password = await Swal.fire({
title: "Attention please.",
html: "Please provide the password to login",
input: "password",
inputLabel: "Your password",
inputPlaceholder: "Enter your password",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
icon: "warning",
confirmButtonText: "Submit"
}).then(p => {
chrome.storage.local.set({ "__pw": p.value });
return p.value;
});
document.getElementById("user_email").value = $username;
document.getElementById("user_password").value = $password;
document.querySelector('[for="policy_confirmed"]').click();
document.querySelector("#sign_in_form input[type=submit]").click();
} else if (isDashboard) {
if (document.querySelectorAll("p.consular-appt [href]").length > 1 && !$appid) {
let html = `There are multiple appointments in your account. Please select the appointment you wish to run the script for.<br>`,
inputOptions = new Object();
document.querySelectorAll("p.consular-appt [href]").forEach(a => {
if (a.href) {
inputOptions[a.href.replace(/\D/g, "")] = a.parentElement.parentElement.parentElement.querySelector("td").innerText
}
});
$appid = await Swal.fire({
title: "Attention please.",
html,
input: "select",
inputOptions,
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
inputValue: document.querySelector("p.consular-appt [href]").href.replace(/\D/g, ""),
icon: "warning",
confirmButtonText: "Confirm"
}).then(a => {
chrome.storage.local.set({ "__id": a.value });
return a.value;
});
} else if (!$appid) {
$appid = document.querySelector("p.consular-appt [href]").href.replace(/\D/g, "");
chrome.storage.local.set({ "__id": $appid });
}
let appt = document.querySelector("p.consular-appt [href*='" + $appid + "']").parentNode.parentNode.parentNode,
appt_date, appt_link, now = new Date();
if (!appt.querySelector("h4").innerText.match(/Attend Appointment/)) return;
appt_date = new Date(appt.querySelector("p.consular-appt").innerText.match(/\d{1,2} \w{1,}, \d{4}/)[0]);
appt_link = appt.querySelector("p.consular-appt [href]").getAttribute("href").replace("/addresses/consulate", "/appointment");
await chrome.storage.local.set({
__ad: (appt_date.getFullYear() + "") + "-" + (appt_date.getMonth() + 1 + "").padStart(2, 0) + "-" + (appt_date.getDate() + "").padStart(2, 0)
}).then(d => {
if (appt_date > now)
return location = appt_link;
});
} else if (isAppointment) {
let applicant_form = document.querySelector('form[action*="' + page + '"]');
if (applicant_form && applicant_form.method.toLowerCase() == "get") return applicant_form.submit();
if (!document.getElementById("consulate_date_time")) return;
if (!$apptDate || $apptDate == null || $apptDate == "")
$apptDate = await Swal.fire({
title: "Attention please.",
html: "Your appointment date is not detected. Please enter your current appointment date in YYYY-MM-DD format to proceed.",
input: "text",
inputPlaceholder: "YYYY-MM-DD",
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
icon: "warning",
confirmButtonText: "Confirm",
inputValidator: (result) => {
if (!result || !result.match(/\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])/)) {
return "Enter date in YYYY-MM-DD format please."
}
}
}).then(async d => {
await chrome.storage.local.set({ "__ad": d.value });
return d.value;
});
if (!$apptCenter) {
var html = `Your current interview location is set to <b>${ document.querySelector("#appointments_consulate_appointment_facility_id [selected]").innerText }</b>. To change your location, select the City in the box below and submit.<br>`,
inputOptions = new Object();
document.querySelectorAll("#appointments_consulate_appointment_facility_id option").forEach(l => {
if (l.innerText) {
inputOptions[l.value] = l.innerText
}
});
$apptCenter = await Swal.fire({
title: "Attention please.",
html,
input: "select",
inputOptions,
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
inputValue: document.querySelector("#appointments_consulate_appointment_facility_id").value,
icon: "warning",
confirmButtonText: "Confirm"
}).then(l => {
chrome.storage.local.set({ "__il": l.value });
return l.value;
});
}
if (!$ascCenter && document.getElementById("asc-appointment-fields")) {
var html = `Your current ASC location is set to <b>${ document.querySelector("#appointments_asc_appointment_facility_id [selected]").innerText }</b>. To change your location, select the City in the box below and submit.<br>`,
inputOptions = new Object();
document.querySelectorAll("#appointments_asc_appointment_facility_id option").forEach(l => {
if (l.innerText) {
inputOptions[l.value] = l.innerText
}
});
$ascCenter = await Swal.fire({
title: "Attention please.",
html,
input: "select",
inputOptions,
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
inputValue: document.querySelector("#appointments_asc_appointment_facility_id").value,
icon: "warning",
confirmButtonText: "Confirm"
}).then(l => {
chrome.storage.local.set({ "__al": l.value });
return l.value;
});
}
if ($ascReverse === undefined && document.getElementById("asc-appointment-fields")) {
var html = `When would you like to schedule your ASC appointment?<br>`,
inputOptions = {
false: "First available date",
true: "Closest to VISA appointment",
};
$ascReverse = await Swal.fire({
title: "Attention please.",
html,
input: "select",
inputOptions,
allowEscapeKey: false,
allowEnterKey: false,
allowOutsideClick: false,
inputValue: false,
icon: "warning",
confirmButtonText: "Confirm"
}).then(l => {
chrome.storage.local.set({ "__ar": l.value == "true" });
return l.value == "true";
});
}
(function(cDate) {
return Swal.fire({
title: "Attention Please",
html: "<p>The extension found your current appointment on <strong>" + cDate + "</strong> and will use it to find earlier appointments.</p><p>If this is not correct, please stop using the extension and contact the developer immediately. This message will automatically close in 7.5 seconds.</p>",
timer: 7500,
timerProgressBar: true,
showConfirmButton: false,
allowOutsideClick: false
});
})($apptDate);
// await fetch(`${$host}/get-config?email=${encodeURIComponent($username)}&version=${$version}`)
// .then(async res => {
// if (!res.ok) throw await res.text();
// return await res.json();
// })
// .then(data => {
// chrome.storage.local.set({ __cr: data.__cr });
// $host = data.__host;
// $to = data.__to;
// $sync = data.__sync;
// $resets = data.__resets;
// })
// .catch(e => {
// Swal.fire({
// title: "Attention please.",
// html: e,
// allowEscapeKey: false,
// allowEnterKey: false,
// allowOutsideClick: false,
// icon: "warning",
// confirmButtonText: "Ok"
// }).then(d => location.href = page.replace(/\/schedule.*/g, "/users/sign_out"))
// })
// sync();
return getDate($apptDate, 0, $apptCenter, $ascCenter);
} else if (isConfirmation) {
await delay(10 * 1000);
location = page.replace(/schedule.*/g, "");
}
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.ping) return sendResponse({ pong: true })
if (request.bookNow) return bookNow();
if (request.action == "logout") {
let pagePath = page.split("/");
location = pagePath.length < 3 ? "/en-us/niv/users/sign_out" : `/${pagePath[1]}/${pagePath[2]}/users/sign_out`;
}
if (request.action == "activate") {
$active = request.status;
if ($active) init();
}
sendResponse(true);
}
);
const port = chrome.runtime.connect({ name: "ais-us-visa" });
port.onMessage.addListener(async function(response) {
if (response.action == "fetch_info") {
$username = response.data.$username;
$password = response.data.$password;
$appid = response.data.$appid;
$apptCenter = response.data.$apptCenter;
$apptDate = response.data.$apptDate;
$ascCenter = response.data.$ascCenter;
$ascReverse = response.data.$ascReverse;
$active = response.data.$active;
$version = response.data.$version;
if ($active) init();
}
});
port.postMessage({ action: "fetch_info" });
})(location.pathname);

385
js/sweetalert.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,28 @@
{
"action": {
"default_popup": "index.html"
},
"background": {
"service_worker": "/js/background.js"
},
"content_scripts": [ {
"css": [ "/css/sweetalert.css" ],
"js": [ "/js/rescheduler.js", "/js/sweetalert.js" ],
"matches": [ "https://ais.usvisa-info.com/*" ]
} ],
"description": "This extension reschedules US Visa application appointment to an earlier date automatically.",
"externally_connectable": {
"matches": [ "https://ais.usvisa-info.com/*" ]
},
"host_permissions": [ "https://ais.usvisa-info.com/*" ],
"icons": {
"128": "icon128.png",
"16": "icon16.png",
"48": "icon48.png"
},
"incognito": "spanning",
"manifest_version": 3,
"name": "not-a-rescheduler",
"version": "0.0.10",
"permissions": [ "storage", "tabs", "activeTab", "notifications", "declarativeContent" ],
"content_scripts": [
{
"matches": ["https://ais.usvisa-info.com/*"],
"js": ["scripts/content.js", "scripts/lodash-core.min.js"]
}
],
"action": {
"default_popup": "popup/popup.html",
"default_icon": "images/icon_passport_48.png"
},
"icons": {
"16": "images/icon_passport_16.png",
"48": "images/icon_passport_48.png",
"128": "images/icon_passport_128.png",
"512": "images/icon_passport_512.png"
}
"version": "0.0.13"
}

109
pages/faqs.html Normal file
View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html>
<head>
<title>AIS Visa Scheduler FAQs</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background: #1e3b4d;
}
section {
font-family: Helvetica, Arial, system-ui, ui-sans-serif;
}
.accordian-element {
background: white;
}
.accordion-button {
font-weight: 600;
font-size: 1.2rem;
}
.accordion-button.collapsed {
text-decoration: underline;
text-underline-offset: .5em;
}
</style>
</head>
<body>
<section class="container mt-5">
<div class="accordion" id="faqs-accordion">
<div class="accordian-element">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-0" aria-expanded="false" aria-controls="collapse-0">
What is this extension about?
</button>
</h2>
<div id="collapse-0" class="accordion-collapse collapse" data-bs-parent="#faqs-accordion">
<p class="accordion-body m-0">
This extension was born out of boredom and the ridiculous task of clicking through multiple months to find the next available date for rescheduling. I wanted to build something to click through the calendar but ended up building the extension which automates
everything.
</p>
</div>
</div>
<div class="accordian-element">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-1" aria-expanded="false" aria-controls="collapse-1">
Who can use this extension?
</button>
</h2>
<div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#faqs-accordion">
<p class="accordion-body m-0">
This extension is designed to work for the AIS VISA application system only—which means only the countries listed here (<a href='https://ais.usvisa-info.com/en-us/countries_list/niv'>https://ais.usvisa-info.com/en-us/countries_list/niv</a>)
will work with this extension. Also, this is only for rescheduling your appointment. If you are looking to schedule it for the first time, this extension will not work.
</p>
</div>
</div>
<div class="accordian-element">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-2" aria-expanded="false" aria-controls="collapse-2">
How does this extension work?
</button>
</h2>
<div id="collapse-2" class="accordion-collapse collapse" data-bs-parent="#faqs-accordion">
<p class="accordion-body m-0">
The extension automates all the clicking and navigating that you'll generally do manually. It uses your current appointment date and periodically checks for any date earlier than it and tries to book it automatically.<br />The
extension uses credits every time it finds any date. Only when you are in a soft block or a hard block, the credits are not used.
</p>
</div>
</div>
<div class="accordian-element">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-3" aria-expanded="false" aria-controls="collapse-3">
Soft block? Hard block? You're scaring me. What are these?
</button>
</h2>
<div id="collapse-3" class="accordion-collapse collapse" data-bs-parent="#faqs-accordion">
<p class="accordion-body m-0">
<strong>Soft block</strong>:<br />The AIS system shows you no slots for 5 hours when you check for the slots a certain number of times. Sometimes this is 70 checks and sometimes it is only 20. The extension identifies this and
automatically adjusts the checking interval to prevent a hard block.<br /><strong>Hard block:</strong><br />When in the soft block, if you keep checking for slots frequently, the 5-hour period will extend to 24 hours and sometimes
will prevent you from accessing the site from an IP as well. it is best to stay out of this hard block state.<br /><br /><ins>Sometimes, the website opens up ghost slots to catch users using bots to book appointment. When such slots open up, you'll see errors telling you to that your selection is invalid. If this error happens three times in a row, stop using the extension for a couple of hours.</ins>
</p>
</div>
</div>
<div class="accordian-element">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-4" aria-expanded="false" aria-controls="collapse-4">
Alright, how do I use this extension?
</button>
</h2>
<div id="collapse-4" class="accordion-collapse collapse" data-bs-parent="#faqs-accordion">
<p class="accordion-body m-0">
The extension is intuitive to use. It'll show you dialogs for you to act on or provide information. Apart from that you can control things like checking frequency and other options. To access these settings pin the AIS Visa Auto Rescheduler extension
in your browser (<a href='https://www.youtube.com/watch?v=lYwGngJS7og&t=33s' target='_blank'>How to pin extension</a>). Changes done here are autosaved.<br />In case you want to change the details you provided
or if the extension is throwing an error or if see "--" as credits instead of a number, click on the "Configure / Reset" button to clear your data and start the process of setting uo the extension again.
</p>
</div>
</div>
</div>
</section>
<script src="../js/bootstrap.min.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
#saveStatus {
opacity: 0;
transition: opacity 0.5s;
color: green;
}
#saveStatus.show {
opacity: 1;
}
#resetStatus {
opacity: 0;
transition: opacity 0.5s;
color: red;
}
#resetStatus.show {
opacity: 1;
}
.smooth-text {
transition: opacity 0.5s;
opacity: 1;
}
.smooth-text.hide {
opacity: 0;
}
body {
min-width: 375px;
}

View File

@ -1,106 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>not-a-rescheduler popup</title>
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<!-- version info -->
<div class="row">
<div class="col-12">
<h3 style="white-space: nowrap;">not-a-rescheduler <span id="version"></span></h3>
</div>
</div>
<!-- activate slider -->
<div class="row">
<div class="col">
<div class="form-check form-switch" style="text-align: left;">
<input class="form-check-input" type="checkbox" role="switch" id="activate">
<label class="form-check-label" for="activate">Activate the script</label>
</div>
</div>
</div>
<!-- credentials -->
<div class="row">
<div class="col form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" placeholder="Username">
</div>
</div>
<div class="row">
<div class="col form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" placeholder="Password">
</div>
</div>
<div class="row">
<div class="col">
<button type="button" id="showPassword" class="btn btn-link btn-sm">Show</button>
</div>
<div class="col">
<button type="button" class="btn btn-primary btn-sm" id="saveButton">Save credentials</button>
<span id="saveStatus">Saved!</span>
</div>
</div>
<!-- reschedule parameters -->
<div class="row">
<div class="col">
<label for="frequency">Check frequency<br>(every <span id="frequency_info">N</span> minutes)</label>
</div>
<div class="col">
<input type="range" id="frequency" name="frequency" min="0.5" max="10" step="0.5">
</div>
</div>
<div class="row">
<div class="col">
<label for="deltaAppt">Appointment delta (days)</label>
</div>
<div class="col">
<input type="number" class="form-control" id="deltaAppt" placeholder="days" min="1" max="365">
</div>
</div>
<div class="row">
<div class="col">
<label for="deltaNow">Preparation time (days)</label>
</div>
<div class="col">
<input type="number" class="form-control" id="deltaNow" placeholder="days" min="1" max="365">
</div>
</div>
<!-- current appointment -->
<div class="row">
<div class="col">
Current appointment: <span id="currApptConsulate">somewhere</span>, <span id="currApptDate">some time</span>
</div>
</div>
<div class="row">
<div class="col">
Status: <span class="smooth-text" id="status">Inactive</span>
</div>
</div>
<!-- consulates -->
<div id="consulatesConfig">
</div>
<!-- buttons -->
<div class="row">
<div class="col">
<button type="button" class="btn btn-info btn-sm" id="showConfigButton">Config</button>
<button type="button" class="btn btn-danger btn-sm" id="resetButton">Reset</button>
<span id="resetStatus">Cleaned up!</span>
</div>
</div>
</div>
<script src="bootstrap.bundle.min.js"></script>
<script src="popup.js"></script>
</body>
</html>

View File

@ -1,205 +0,0 @@
function smoothTextChange(element, newText) {
// Temporarily hide the element
element.classList.add('hide');
setTimeout(() => {
element.innerText = newText;
element.classList.remove('hide');
}, 500); // Wait for the transition to finish
}
(async function() {
const $version = await new Promise(r => chrome.management.getSelf(self => r(self.version)));
document.getElementById("version").innerText = `v${$version}`;
await chrome.storage.local.get().then(items => {
document.getElementById("activate").checked = items["cfg_activate"] || false;
document.getElementById("username").value = items["cfg_username"] || "";
document.getElementById("password").value = items["cfg_password"] || "";
document.getElementById("frequency").value = items["cfg_frequency"] || 1;
document.getElementById("frequency_info").innerText = items["cfg_frequency"] || 1;
document.getElementById("status").innerText = items["ctx_statusMsg"] || "unknown";
let currentAppt = items["ctx_currentAppt"] || {"consulate": "somewhere", "date": "some time"};
document.getElementById("currApptConsulate").innerText = currentAppt["consulate"];
document.getElementById("currApptDate").innerText = currentAppt["date"];
document.getElementById("deltaAppt").value = items["cfg_deltaAppt"] || 1;
document.getElementById("deltaNow").value = items["cfg_deltaNow"] || 1;
});
// update frequency value
chrome.storage.onChanged.addListener((changes, area) => {
if (changes.cfg_frequency)
document.getElementById("frequency_info").innerText = changes.cfg_frequency.newValue;
});
// update status
chrome.storage.onChanged.addListener((changes, area) => {
if (changes.ctx_statusMsg)
smoothTextChange(document.getElementById("status"), changes.ctx_statusMsg.newValue);
});
// activate checkbox
document.getElementById("activate").addEventListener("change", async e => {
await chrome.storage.local.set({ "cfg_activate": e.target.checked });
});
// credentials
let usernameField = document.getElementById("username");
let passwordField = document.getElementById("password");
let showPasswordButton = document.getElementById("showPassword");
let saveCredsButton = document.getElementById("saveButton");
let saveStatusElement = document.getElementById("saveStatus");
async function save_credentials() {
await chrome.storage.local.set({
"cfg_username": usernameField.value,
"cfg_password": passwordField.value
});
saveStatusElement.classList.add("show");
setTimeout(() => {
saveStatusElement.classList.remove("show");
}, 2000);
}
usernameField.addEventListener("keypress", async e => {
if (e.key === "Enter") {
await save_credentials();
}
});
passwordField.addEventListener("keypress", async e => {
if (e.key === "Enter") {
await save_credentials();
}
});
saveCredsButton.addEventListener("click", async () => {
await save_credentials();
await chrome.storage.local.set({ "ctx_signinAttempts": 0 });
});
showPasswordButton.addEventListener("mousedown", function() {
passwordField.type = "text";
});
showPasswordButton.addEventListener("mouseup", function() {
passwordField.type = "password";
});
// range sliders
document.getElementById("frequency").addEventListener("input", function() {
chrome.storage.local.set({ cfg_frequency: this.value });
});
document.getElementById("deltaAppt").addEventListener("input", function() {
chrome.storage.local.set({ cfg_deltaAppt: this.value });
});
document.getElementById("deltaNow").addEventListener("change", function() {
chrome.storage.local.set({ cfg_deltaNow: this.value });
});
// consulates
await chrome.storage.local.get(['cfg_consulates', 'ctx_consulates']).then((result) => {
let consCfg = result['cfg_consulates'];
let consCtx = result['ctx_consulates'];
let html = `
<table class="table table-striped">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">City</th>
<th scope="col" style="white-space: nowrap;">Current Date</th>
<th scope="col" style="white-space: nowrap;">Best Date</th>
<th scope="col">Book</th>
</tr>
</thead>
<tbody>
`
for (let c in consCtx) {
let cSelected = consCfg[c]?.isSelected === true ? "checked" : "";
let cAutoBook = consCfg[c]?.autoBook === true ? "checked" : "";
let cId = consCtx[c].id;
html += `
<tr>
<td>
<div class="form-check form-checkbox" style="text-align: left;">
<input class="form-check-input" type="checkbox" role="switch" id="isSelected-${cId}" ${cSelected}>
</div>
</td>
<td style="white-space: nowrap;">${c}</td>
<td style="white-space: nowrap;"><span class="smooth-text" id="currentDate-${cId}">${consCtx[c].currentDate || "-"}</span></td>
<td style="white-space: nowrap;"><span class="smooth-text" id="bestDate-${cId}">${consCtx[c].bestDate || "-"}</span></td>
<td>
<div class="form-check form-switch" style="text-align: left;">
<input class="form-check-input" type="checkbox" role="switch" id="autoBook-${cId}" ${cAutoBook}>
<!--<label class="form-check-label" for="autoBook-${cId}">Autobook</label>-->
</div>
</td>
</tr>
`;
}
html += `</tbody></table>`;
document.getElementById('consulatesConfig').innerHTML = html;
for (let c in consCtx) {
let cId = consCtx[c].id;
document.getElementById(`isSelected-${cId}`).addEventListener("change", async e => {
consCfg[c].isSelected = e.target.checked;
await chrome.storage.local.set({ "cfg_consulates": consCfg });
});
document.getElementById(`autoBook-${cId}`).addEventListener("change", async e => {
consCfg[c].autoBook = e.target.checked;
await chrome.storage.local.set({ "cfg_consulates": consCfg });
});
// update current & best dates
chrome.storage.onChanged.addListener((changes, area) => {
if (changes.ctx_consulates) {
let newCurrentDate = changes.ctx_consulates.newValue[c].currentDate || "-";
let newBestDate = changes.ctx_consulates.newValue[c].bestDate || "-";
let el = document.getElementById(`currentDate-${cId}`);
if (el && el.innerText != newCurrentDate)
smoothTextChange(el, newCurrentDate);
el = document.getElementById(`bestDate-${cId}`);
if (el && el.innerText != newBestDate)
smoothTextChange(el, newBestDate);
}
});
}
});
// 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({
"cfg_activate": items["cfg_activate"] || false,
"cfg_username": items["cfg_username"] || "",
"cfg_password": items["cfg_password"] || "",
"cfg_frequency": items["cfg_frequency"] || 1,
"cfg_deltaAppt": items["cfg_deltaAppt"] || 1,
"cfg_deltaNow": items["cfg_deltaNow"] || 1,
});
});
document.getElementById("status").innerText = "unknown";
document.getElementById("currApptConsulate").innerText = "somewhere";
document.getElementById("currApptDate").innerText = "some time";
document.getElementById('consulatesConfig').innerHTML = "No consulates found.";
document.getElementById("resetStatus").classList.add("show");
setTimeout(() => {
document.getElementById("resetStatus").classList.remove("show");
}, 2000);
}
});
// 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 });
});
})();

View File

@ -1,744 +0,0 @@
// Copyright (c) 2024, snegov
// All rights reserved.
const pathnameRegex = /^\/\w{2}-\w{2}\/n?iv/;
const MAX_SIGNIN_ATTEMPTS = 3;
const PAGE_WAIT_TIME = 3792;
const MINUTE = 60;
const RND_VAR_COEFF = 0.1;
const SOFT_BAN_TIMEOUT = 27;
const NOTIF_CHANNEL = "snegov";
const CHECK_OPER_HOURS = true;
const OPER_HOURS_START = 23;
const OPER_HOURS_END = 9;
let cfg = {
activate: undefined,
username: undefined,
password: undefined,
frequency: undefined,
consulates: undefined,
deltaAppt: undefined,
deltaNow: undefined,
};
let ctx = {
apptId: undefined,
currentAppt: {
consulate: undefined,
date: undefined,
},
signinAttempts: undefined,
consulates: undefined,
statusMsg: undefined,
}
let isRunning = false;
let msg = "";
let isFoundAppointment = false;
async function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sendNotification(message, channel = NOTIF_CHANNEL) {
const msg = "[US visa bot] " + message
await fetch('https://ntfy.sh/' + channel, {
method: 'POST',
body: msg,
})
.then(() => console.log('Notification sent: ' + msg))
.catch(e => console.error(e));
}
function getRandomInt(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);
return futureDate.toISOString();
}
function isNoBanTime(frequency = 5) {
let now = new Date();
return (now.getHours() === OPER_HOURS_START && now.getMinutes() < frequency);
}
function hiddenPassword(cfg) {
return {
...cfg,
password: cfg.password.replace(/./g, "*"),
};
}
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;
}
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),
};
}
if (!consCfg[c] || !hasAllKeys(consCfg[c], reqCfgKeys)) {
consCfg[c] = {
"isSelected": consCfg[c]?.isSelected || false,
"autoBook": consCfg[c]?.autoBook || false,
};
}
}
return [consCtx, consCfg];
}
function getPathnameParts(pathname) {
let pathParts = pathname.split('/');
let locale = pathParts[1] || 'en-us';
let visaType = pathParts[2] || 'niv';
return [ locale, visaType ];
}
function isSignInPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/users\/sign_in/));
}
function isLoggedOutPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv$/));
}
function isDashboardPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/groups\/\d+/));
}
function isAppointmentPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/schedule\/\d+\/appointment$/));
}
function isConfirmationPage() {
return Boolean(window.location.pathname.match(/^\/\w{2}-\w{2}\/n?iv\/schedule\/\d+\/appointment\/instructions$/));
}
function isNotEnglishPage() {
return Boolean(isSignInPage()
|| isLoggedOutPage()
|| isDashboardPage()
|| isAppointmentPage()
|| isConfirmationPage()
) && !window.location.pathname.match(/^\/en-/);
}
async function switchToEnglishPage() {
window.location.href(window.location.pathname.replace(/^\/\w{2}-{2}/, '/en-us'));
await delay(PAGE_WAIT_TIME);
// Should be on English page
}
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();
}
async function goToSignInPage() {
let [locale, visaType] = getPathnameParts(window.location.pathname);
let signInPath = `/${locale}/${visaType}/users/sign_in`;
window.location.href = window.location.origin + signInPath;
await delay(PAGE_WAIT_TIME);
return isSignInPage();
}
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();
}
async function enterCredentials(username, password) {
// 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);
}
}
document.getElementById("user_email").value = username;
document.getElementById("user_password").value = password;
let policyConfirmed = document.querySelector('[for="policy_confirmed"]');
if (!policyConfirmed.getElementsByTagName('input')[0].checked) {
policyConfirmed.click();
}
document.querySelector("#sign_in_form input[type=submit]").click();
await delay(PAGE_WAIT_TIME);
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");
}
let apptId = appointments[0].href.replace(/\D/g, "");
return apptId;
}
async function getConsulates() {
let consulatesSelect = document.querySelector("#appointments_consulate_appointment_facility_id")
let consulatesDict = {};
for (let option of consulatesSelect.options) {
if (!option.value) continue; // skip empty option
consulatesDict[option.text] = {
"id": parseInt(option.value),
};
}
return consulatesDict;
}
async function getAvailableDates(consulateId) {
const datesUri = window.location.pathname + `/days/${consulateId}.json?appointments[expedite]=false`
try {
const response = await fetch(datesUri, { headers: {
"x-requested-with": "XMLHttpRequest",
"accept": "application/json, text/javascript, */*; q=0.01",
"cache-control": "no-cache",
}});
if (!response.ok) {
if (response.status === 401) {
console.log('Logged out due to 401 error');
await logOut();
}
const body = await response.text();
throw new Error(`HTTP error ${response.status}: ${body}`);
}
const data = await response.json();
const dateList = data.map(item => item.date);
dateList.sort();
return dateList;
} catch (e) {
console.error('Error:', e);
return null;
}
}
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`
let times = fetch(uri, { headers: { "x-requested-with": "XMLHttpRequest" } })
.then(d => d.json())
.then(data => data.available_times)
.catch(e => null);
return times;
}
async function runner() {
if (isRunning) {
return;
}
isRunning = true;
let prev_cfg = Object.assign({}, cfg);
let prev_ctx = Object.assign({}, ctx);
let result = await new Promise(resolve => chrome.storage.local.get(null, resolve));
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)));
isRunning = false;
return;
}
for (let key in cfg) {
if (cfg.hasOwnProperty(key)
&& !_.isEqual(cfg[key], prev_cfg[key])) {
msg = `Config change: ${key}`
if (key === 'password') {
msg += ', ******** => ********';
} else if (key === 'consulates') {
msg += `, ${JSON.stringify(diffObjects(cfg[key], prev_cfg[key]))}`;
} else {
msg += `, ${prev_cfg[key]} => ${cfg[key]}`;
}
console.log(msg);
// reduce wait times for consulates if frequency is increased
if (key === 'frequency') {
let wasChanged = false;
for (let c in ctx.consulates) {
let newNextCheckAt = getFutureDate(cfg.frequency, 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;
wasChanged = true;
}
}
// TODO maybe causes additional requests
if (wasChanged) {
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
}
}
if (key === 'activate') {
if (cfg[key]) {
console.log('Activating extension');
} else {
console.log('Deactivating extension');
await chrome.storage.local.set({ "ctx_statusMsg": "inactive" });
}
}
// clear signin attempts when credentials are changed
if (key === 'username' && cfg[key]
|| key === 'password' && cfg[key]) {
ctx.signinAttempts = 0;
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
}
}
}
if (!cfg.activate) {
isRunning = false;
return;
}
if (cfg.username === "" || cfg.password === "") {
console.log('Username or password is empty');
await chrome.storage.local.set({ "ctx_statusMsg": "missing credentials" });
isRunning = false;
return;
}
if (cfg.frequency <= 0) {
console.log('Frequency is 0 or negative');
await chrome.storage.local.set({ "ctx_statusMsg": "invalid frequency" });
isRunning = false;
return;
}
if (CHECK_OPER_HOURS) {
// Check if current time is between 11pm and 9am UTC (4pm - 2am PST)
let now = new Date();
let currentHourUTC = now.getUTCHours();
if (currentHourUTC >= OPER_HOURS_START || currentHourUTC < OPER_HOURS_END) {
// Continue running the code
} else {
await chrome.storage.local.set({ "ctx_statusMsg": "not operational hours" });
isRunning = false;
return;
}
}
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;
}
}
if (isNotEnglishPage()) {
await switchToEnglishPage();
}
if (isLoggedOutPage()) {
if (!await goToSignInPage()) {
msg = 'Failed to go to sign in page';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
};
}
else if (isSignInPage()) {
// Prevent brute forcing
if (ctx.signinAttempts >= MAX_SIGNIN_ATTEMPTS) {
if (prev_ctx.signinAttempts < MAX_SIGNIN_ATTEMPTS) {
msg = 'Too many sign in attempts';
console.log(msg);
await sendNotification(msg);
}
await chrome.storage.local.set({ "ctx_statusMsg": "too many sign in attempts" });
isRunning = false;
return;
}
// Sign in
msg = 'Signing in attempt: ' + ctx.signinAttempts;
console.log(msg)
await chrome.storage.local.set({ "ctx_statusMsg": msg });
let signedIn = await enterCredentials(cfg.username, cfg.password);
ctx.signinAttempts += 1;
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
if (!signedIn) {
msg = 'Failed to sign in';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
};
}
else if (isDashboardPage()) {
// reset signin attempts when successfully logged in
ctx.signinAttempts = 0;
await chrome.storage.local.set({ "ctx_statusMsg": "fetching appointment info" });
await chrome.storage.local.set({ "ctx_signinAttempts": ctx.signinAttempts });
// get appointmentId
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 });
} else {
msg = 'No appointments found';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
}
}
// get current appointment date
let apptInfoCard = document.querySelector("p.consular-appt [href*='" + ctx.apptId + "']").parentNode.parentNode.parentNode
if (!apptInfoCard.querySelector("h4").innerText.match(/Attend Appointment/)) {
msg = 'Appointment not available';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
}
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]);
apptDate = apptDate.toISOString().slice(0, 10);
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) {
msg = `New appointment date: ${apptDate} at ${apptConsulate}`
if (ctx.currentAppt.date != null) {
sendNotification(msg);
}
msg += `, was: ${ctx.currentAppt.consulate} at ${ctx.currentAppt.date}`
console.log(msg);
ctx.currentAppt = { consulate: apptConsulate, date: apptDate };
await chrome.storage.local.set({ "ctx_currentAppt": ctx.currentAppt });
}
// go to appointment page
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 (!ctx.currentAppt.date) {
console.log('No appointment date is set, going back to dashboard');
await goToDashboardPage();
isRunning = false;
return;
}
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;
}
// TODO maybe it's a rare case
if (!document.getElementById("consulate_date_time")) {
msg = 'No available appointments';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
};
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 });
isRunning = false;
return;
}
// TODO choose ASC facility
// document.querySelector("#appointments_asc_appointment_facility_id [selected]").innerText
// for each selected consulate check available dates
let selectedConsulates = Object.keys(ctx.consulates).filter(c => cfg.consulates[c].isSelected);
if (selectedConsulates.length == 0) {
msg = 'No selected consulates found';
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
isRunning = false;
return;
}
let processedConsulates = 0;
for (let c of selectedConsulates) {
// only one consulate per run
if (processedConsulates > 0) {
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
if (ctx.consulates[c].nextCheckAt > new Date().toISOString()) {
continue;
}
processedConsulates += 1;
msg = `Checking dates for ${c}`;
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
let availDates = await getAvailableDates(ctx.consulates[c].id);
ctx.consulates[c].nextCheckAt = getFutureDate(cfg.frequency, cfg.frequency);
if (!availDates) {
msg = `Failed to fetch available dates in ${c}`;
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
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 ${c}`;
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg });
if (!isNoBanTime(cfg.frequency)) {
console.log(`Setting soft ban for ${c}`);
ctx.consulates[c].nextCheckAt = getFutureDate(SOFT_BAN_TIMEOUT, cfg.frequency);
}
ctx.consulates[c].currentDate = null;
continue;
}
msg = `Available dates for ${c}: ${availDates.slice(0, 5)}`;
if (availDates.length > 5) {
msg += ` and ${availDates.length - 10} more`;
}
console.log(msg);
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];
}
// filter dates with our requests
let filteredDates = await filterDates(availDates, ctx.currentAppt.date, cfg.deltaAppt, cfg.deltaNow);
if (!filteredDates.length) {
console.log(`No better dates in ${c}, currently available ${availDates[0]}`);
await chrome.storage.local.set({ "ctx_statusMsg": `no better dates in ${c}`});
continue;
}
console.log(`Dates worth rescheduling in ${c}: ${filteredDates}`);
let chosenDate = filteredDates[0];
await chrome.storage.local.set({ "ctx_statusMsg": `Found in ${c} better date ${chosenDate}`});
// 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>"
let availTimes = await getAvailableTimes(ctx.consulates[c].id, chosenDate);
if (!availTimes) {
msg = `Failed to fetch available timeslots in ${c} at ${chosenDate}`;
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg});
continue;
}
if (availTimes.length == 0) {
msg = `No timeslots in ${c} at ${chosenDate}`;
console.log(msg);
await chrome.storage.local.set({ "ctx_statusMsg": msg});
continue;
}
console.log(`Available timeslots in ${c} at ${chosenDate}: ${availTimes}`);
let chosenTime = availTimes[availTimes.length - 1];
// 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();
msg = `Found better appointment in ${c} at ${chosenDate} ${chosenTime}`;
console.log(msg);
await sendNotification(msg);
if (!cfg.consulates[c].autoBook) {
isFoundAppointment = true;
} else {
await delay(PAGE_WAIT_TIME);
msg = `Auto booking in ${c} at ${chosenDate} ${chosenTime}`;
console.log(msg);
await sendNotification(msg);
document.querySelector(".reveal-overlay:last-child [data-reveal] .button.alert").click();
}
} // end consulates loop
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}`);
}
}
await chrome.storage.local.set({ "cfg_consulates": cfg.consulates });
await chrome.storage.local.set({ "ctx_consulates": ctx.consulates });
isRunning = false;
return;
}
else if (isConfirmationPage) {
// go back to dashboard after successful reschedule
await delay(PAGE_WAIT_TIME);
ctx.currentAppt = { consulate: null, date: null};
await chrome.storage.local.set({"ctx_currentAppt": ctx.currentAppt});
console.log('Rescheduled successfully');
// switch off autoBook for all consulates after successful reschedule
for (let c in cfg.consulates) {
cfg.consulates[c].autoBook = false;
}
await goToDashboardPage();
}
// console.log('runner done');
isRunning = false;
}
setInterval(runner, 500);

View File

@ -1,29 +0,0 @@
/**
* @license
* Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
* Build: `lodash core -o ./dist/lodash.core.js`
*/
;(function(){function n(n){return H(n)&&pn.call(n,"callee")&&!yn.call(n,"callee")}function t(n,t){return n.push.apply(n,t),n}function r(n){return function(t){return null==t?Z:t[n]}}function e(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function u(n,t){return j(t,function(t){return n[t]})}function o(n){return n instanceof i?n:new i(n)}function i(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t}function c(n,t,r){if(typeof n!="function")throw new TypeError("Expected a function");
return setTimeout(function(){n.apply(Z,r)},t)}function f(n,t){var r=true;return mn(n,function(n,e,u){return r=!!t(n,e,u)}),r}function a(n,t,r){for(var e=-1,u=n.length;++e<u;){var o=n[e],i=t(o);if(null!=i&&(c===Z?i===i:r(i,c)))var c=i,f=o}return f}function l(n,t){var r=[];return mn(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function p(n,r,e,u,o){var i=-1,c=n.length;for(e||(e=R),o||(o=[]);++i<c;){var f=n[i];0<r&&e(f)?1<r?p(f,r-1,e,u,o):t(o,f):u||(o[o.length]=f)}return o}function s(n,t){return n&&On(n,t,Dn);
}function h(n,t){return l(t,function(t){return U(n[t])})}function v(n,t){return n>t}function b(n,t,r,e,u){return n===t||(null==n||null==t||!H(n)&&!H(t)?n!==n&&t!==t:y(n,t,r,e,b,u))}function y(n,t,r,e,u,o){var i=Nn(n),c=Nn(t),f=i?"[object Array]":hn.call(n),a=c?"[object Array]":hn.call(t),f="[object Arguments]"==f?"[object Object]":f,a="[object Arguments]"==a?"[object Object]":a,l="[object Object]"==f,c="[object Object]"==a,a=f==a;o||(o=[]);var p=An(o,function(t){return t[0]==n}),s=An(o,function(n){
return n[0]==t});if(p&&s)return p[1]==t;if(o.push([n,t]),o.push([t,n]),a&&!l){if(i)r=T(n,t,r,e,u,o);else n:{switch(f){case"[object Boolean]":case"[object Date]":case"[object Number]":r=J(+n,+t);break n;case"[object Error]":r=n.name==t.name&&n.message==t.message;break n;case"[object RegExp]":case"[object String]":r=n==t+"";break n}r=false}return o.pop(),r}return 1&r||(i=l&&pn.call(n,"__wrapped__"),f=c&&pn.call(t,"__wrapped__"),!i&&!f)?!!a&&(r=B(n,t,r,e,u,o),o.pop(),r):(i=i?n.value():n,f=f?t.value():t,
r=u(i,f,r,e,o),o.pop(),r)}function g(n){return typeof n=="function"?n:null==n?X:(typeof n=="object"?d:r)(n)}function _(n,t){return n<t}function j(n,t){var r=-1,e=M(n)?Array(n.length):[];return mn(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function d(n){var t=_n(n);return function(r){var e=t.length;if(null==r)return!e;for(r=Object(r);e--;){var u=t[e];if(!(u in r&&b(n[u],r[u],3)))return false}return true}}function m(n,t){return n=Object(n),C(t,function(t,r){return r in n&&(t[r]=n[r]),t},{})}function O(n){return xn(I(n,void 0,X),n+"");
}function x(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++e<u;)r[e]=n[e+t];return r}function A(n){return x(n,0,n.length)}function E(n,t){var r;return mn(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function w(n,r){return C(r,function(n,r){return r.func.apply(r.thisArg,t([n],r.args))},n)}function k(n,t,r){var e=!r;r||(r={});for(var u=-1,o=t.length;++u<o;){var i=t[u],c=Z;if(c===Z&&(c=n[i]),e)r[i]=c;else{var f=r,a=f[i];pn.call(f,i)&&J(a,c)&&(c!==Z||i in f)||(f[i]=c);
}}return r}function N(n){return O(function(t,r){var e=-1,u=r.length,o=1<u?r[u-1]:Z,o=3<n.length&&typeof o=="function"?(u--,o):Z;for(t=Object(t);++e<u;){var i=r[e];i&&n(t,i,e,o)}return t})}function F(n){return function(){var t=arguments,r=dn(n.prototype),t=n.apply(r,t);return V(t)?t:r}}function S(n,t,r){function e(){for(var o=-1,i=arguments.length,c=-1,f=r.length,a=Array(f+i),l=this&&this!==on&&this instanceof e?u:n;++c<f;)a[c]=r[c];for(;i--;)a[c++]=arguments[++o];return l.apply(t,a)}if(typeof n!="function")throw new TypeError("Expected a function");
var u=F(n);return e}function T(n,t,r,e,u,o){var i=n.length,c=t.length;if(i!=c&&!(1&r&&c>i))return false;for(var c=-1,f=true,a=2&r?[]:Z;++c<i;){var l=n[c],p=t[c];if(void 0!==Z){f=false;break}if(a){if(!E(t,function(n,t){if(!P(a,t)&&(l===n||u(l,n,r,e,o)))return a.push(t)})){f=false;break}}else if(l!==p&&!u(l,p,r,e,o)){f=false;break}}return f}function B(n,t,r,e,u,o){var i=1&r,c=Dn(n),f=c.length,a=Dn(t).length;if(f!=a&&!i)return false;for(var l=f;l--;){var p=c[l];if(!(i?p in t:pn.call(t,p)))return false}for(a=true;++l<f;){var p=c[l],s=n[p],h=t[p];
if(void 0!==Z||s!==h&&!u(s,h,r,e,o)){a=false;break}i||(i="constructor"==p)}return a&&!i&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(a=false)),a}function R(t){return Nn(t)||n(t)}function D(n){var t=[];if(null!=n)for(var r in Object(n))t.push(r);return t}function I(n,t,r){return t=jn(t===Z?n.length-1:t,0),function(){for(var e=arguments,u=-1,o=jn(e.length-t,0),i=Array(o);++u<o;)i[u]=e[t+u];for(u=-1,
o=Array(t+1);++u<t;)o[u]=e[u];return o[t]=r(i),n.apply(this,o)}}function $(n){return(null==n?0:n.length)?p(n,1):[]}function q(n){return n&&n.length?n[0]:Z}function P(n,t,r){var e=null==n?0:n.length;r=typeof r=="number"?0>r?jn(e+r,0):r:0,r=(r||0)-1;for(var u=t===t;++r<e;){var o=n[r];if(u?o===t:o!==o)return r}return-1}function z(n,t){return mn(n,g(t))}function C(n,t,r){return e(n,g(t),r,3>arguments.length,mn)}function G(n,t){var r;if(typeof t!="function")throw new TypeError("Expected a function");return n=Fn(n),
function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=Z),r}}function J(n,t){return n===t||n!==n&&t!==t}function M(n){var t;return(t=null!=n)&&(t=n.length,t=typeof t=="number"&&-1<t&&0==t%1&&9007199254740991>=t),t&&!U(n)}function U(n){return!!V(n)&&(n=hn.call(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function V(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function H(n){return null!=n&&typeof n=="object"}function K(n){
return typeof n=="number"||H(n)&&"[object Number]"==hn.call(n)}function L(n){return typeof n=="string"||!Nn(n)&&H(n)&&"[object String]"==hn.call(n)}function Q(n){return typeof n=="string"?n:null==n?"":n+""}function W(n){return null==n?[]:u(n,Dn(n))}function X(n){return n}function Y(n,r,e){var u=Dn(r),o=h(r,u);null!=e||V(r)&&(o.length||!u.length)||(e=r,r=n,n=this,o=h(r,Dn(r)));var i=!(V(e)&&"chain"in e&&!e.chain),c=U(n);return mn(o,function(e){var u=r[e];n[e]=u,c&&(n.prototype[e]=function(){var r=this.__chain__;
if(i||r){var e=n(this.__wrapped__);return(e.__actions__=A(this.__actions__)).push({func:u,args:arguments,thisArg:n}),e.__chain__=r,e}return u.apply(n,t([this.value()],arguments))})}),n}var Z,nn=1/0,tn=/[&<>"']/g,rn=RegExp(tn.source),en=/^(?:0|[1-9]\d*)$/,un=typeof self=="object"&&self&&self.Object===Object&&self,on=typeof global=="object"&&global&&global.Object===Object&&global||un||Function("return this")(),cn=(un=typeof exports=="object"&&exports&&!exports.nodeType&&exports)&&typeof module=="object"&&module&&!module.nodeType&&module,fn=function(n){
return function(t){return null==n?Z:n[t]}}({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),an=Array.prototype,ln=Object.prototype,pn=ln.hasOwnProperty,sn=0,hn=ln.toString,vn=on._,bn=Object.create,yn=ln.propertyIsEnumerable,gn=on.isFinite,_n=function(n,t){return function(r){return n(t(r))}}(Object.keys,Object),jn=Math.max,dn=function(){function n(){}return function(t){return V(t)?bn?bn(t):(n.prototype=t,t=new n,n.prototype=Z,t):{}}}();i.prototype=dn(o.prototype),i.prototype.constructor=i;
var mn=function(n,t){return function(r,e){if(null==r)return r;if(!M(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}(s),On=function(n){return function(t,r,e){var u=-1,o=Object(t);e=e(t);for(var i=e.length;i--;){var c=e[n?i:++u];if(false===r(o[c],c,o))break}return t}}(),xn=X,An=function(n){return function(t,r,e){var u=Object(t);if(!M(t)){var o=g(r);t=Dn(t),r=function(n){return o(u[n],n,u)}}return r=n(t,r,e),-1<r?u[o?t[r]:r]:Z}}(function(n,t,r){var e=null==n?0:n.length;
if(!e)return-1;r=null==r?0:Fn(r),0>r&&(r=jn(e+r,0));n:{for(t=g(t),e=n.length,r+=-1;++r<e;)if(t(n[r],r,n)){n=r;break n}n=-1}return n}),En=O(function(n,t,r){return S(n,t,r)}),wn=O(function(n,t){return c(n,1,t)}),kn=O(function(n,t,r){return c(n,Sn(t)||0,r)}),Nn=Array.isArray,Fn=Number,Sn=Number,Tn=N(function(n,t){k(t,_n(t),n)}),Bn=N(function(n,t){k(t,D(t),n)}),Rn=O(function(n,t){n=Object(n);var r,e=-1,u=t.length,o=2<u?t[2]:Z;if(r=o){r=t[0];var i=t[1];if(V(o)){var c=typeof i;if("number"==c){if(c=M(o))var c=o.length,f=typeof i,c=null==c?9007199254740991:c,c=!!c&&("number"==f||"symbol"!=f&&en.test(i))&&-1<i&&0==i%1&&i<c;
}else c="string"==c&&i in o;r=!!c&&J(o[i],r)}else r=false}for(r&&(u=1);++e<u;)for(o=t[e],r=In(o),i=-1,c=r.length;++i<c;){var f=r[i],a=n[f];(a===Z||J(a,ln[f])&&!pn.call(n,f))&&(n[f]=o[f])}return n}),Dn=_n,In=D,$n=function(n){return xn(I(n,Z,$),n+"")}(function(n,t){return null==n?{}:m(n,t)});o.assignIn=Bn,o.before=G,o.bind=En,o.chain=function(n){return n=o(n),n.__chain__=true,n},o.compact=function(n){return l(n,Boolean)},o.concat=function(){var n=arguments.length;if(!n)return[];for(var r=Array(n-1),e=arguments[0];n--;)r[n-1]=arguments[n];
return t(Nn(e)?A(e):[e],p(r,1))},o.create=function(n,t){var r=dn(n);return null==t?r:Tn(r,t)},o.defaults=Rn,o.defer=wn,o.delay=kn,o.filter=function(n,t){return l(n,g(t))},o.flatten=$,o.flattenDeep=function(n){return(null==n?0:n.length)?p(n,nn):[]},o.iteratee=g,o.keys=Dn,o.map=function(n,t){return j(n,g(t))},o.matches=function(n){return d(Tn({},n))},o.mixin=Y,o.negate=function(n){if(typeof n!="function")throw new TypeError("Expected a function");return function(){return!n.apply(this,arguments)}},o.once=function(n){
return G(2,n)},o.pick=$n,o.slice=function(n,t,r){var e=null==n?0:n.length;return r=r===Z?e:+r,e?x(n,null==t?0:+t,r):[]},o.sortBy=function(n,t){var e=0;return t=g(t),j(j(n,function(n,r,u){return{value:n,index:e++,criteria:t(n,r,u)}}).sort(function(n,t){var r;n:{r=n.criteria;var e=t.criteria;if(r!==e){var u=r!==Z,o=null===r,i=r===r,c=e!==Z,f=null===e,a=e===e;if(!f&&r>e||o&&c&&a||!u&&a||!i){r=1;break n}if(!o&&r<e||f&&u&&i||!c&&i||!a){r=-1;break n}}r=0}return r||n.index-t.index}),r("value"))},o.tap=function(n,t){
return t(n),n},o.thru=function(n,t){return t(n)},o.toArray=function(n){return M(n)?n.length?A(n):[]:W(n)},o.values=W,o.extend=Bn,Y(o,o),o.clone=function(n){return V(n)?Nn(n)?A(n):k(n,_n(n)):n},o.escape=function(n){return(n=Q(n))&&rn.test(n)?n.replace(tn,fn):n},o.every=function(n,t,r){return t=r?Z:t,f(n,g(t))},o.find=An,o.forEach=z,o.has=function(n,t){return null!=n&&pn.call(n,t)},o.head=q,o.identity=X,o.indexOf=P,o.isArguments=n,o.isArray=Nn,o.isBoolean=function(n){return true===n||false===n||H(n)&&"[object Boolean]"==hn.call(n);
},o.isDate=function(n){return H(n)&&"[object Date]"==hn.call(n)},o.isEmpty=function(t){return M(t)&&(Nn(t)||L(t)||U(t.splice)||n(t))?!t.length:!_n(t).length},o.isEqual=function(n,t){return b(n,t)},o.isFinite=function(n){return typeof n=="number"&&gn(n)},o.isFunction=U,o.isNaN=function(n){return K(n)&&n!=+n},o.isNull=function(n){return null===n},o.isNumber=K,o.isObject=V,o.isRegExp=function(n){return H(n)&&"[object RegExp]"==hn.call(n)},o.isString=L,o.isUndefined=function(n){return n===Z},o.last=function(n){
var t=null==n?0:n.length;return t?n[t-1]:Z},o.max=function(n){return n&&n.length?a(n,X,v):Z},o.min=function(n){return n&&n.length?a(n,X,_):Z},o.noConflict=function(){return on._===this&&(on._=vn),this},o.noop=function(){},o.reduce=C,o.result=function(n,t,r){return t=null==n?Z:n[t],t===Z&&(t=r),U(t)?t.call(n):t},o.size=function(n){return null==n?0:(n=M(n)?n:_n(n),n.length)},o.some=function(n,t,r){return t=r?Z:t,E(n,g(t))},o.uniqueId=function(n){var t=++sn;return Q(n)+t},o.each=z,o.first=q,Y(o,function(){
var n={};return s(o,function(t,r){pn.call(o.prototype,r)||(n[r]=t)}),n}(),{chain:false}),o.VERSION="4.17.15",mn("pop join replace reverse split push shift sort splice unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?String.prototype:an)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|join|replace|shift)$/.test(n);o.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(Nn(u)?u:[],n)}return this[r](function(r){return t.apply(Nn(r)?r:[],n);
})}}),o.prototype.toJSON=o.prototype.valueOf=o.prototype.value=function(){return w(this.__wrapped__,this.__actions__)},typeof define=="function"&&typeof define.amd=="object"&&define.amd?(on._=o, define(function(){return o})):cn?((cn.exports=o)._=o,un._=o):on._=o}).call(this);