Skip to content

Instantly share code, notes, and snippets.

@Ruturaj4
Last active September 16, 2024 01:04
Show Gist options
  • Save Ruturaj4/99ee7179700041ffea3e107093c8e598 to your computer and use it in GitHub Desktop.
Save Ruturaj4/99ee7179700041ffea3e107093c8e598 to your computer and use it in GitHub Desktop.
const CONSTS = {
CITY_SELECT: 'select#post_select',
SELECT_MONTH: 'select[data-handler="selectMonth"]',
SELECT_YEAR: 'select[data-handler="selectYear"]',
PREV_BTN: 'a[data-handler="prev"]',
NEXT_BTN: 'a[data-handler="next"]',
DATE_PICKER: 'div#datepicker.hasDatepicker',
GREEN_DAY: 'div#datepicker table .greenday',
// DATA_MONTH: 'data-month',
// DATA_YEAR: 'data-year',
TIME_SELECT: 'table#time_select tr td input',
// SLOT_TIME: 'table#time_select tr td:nth-child(2)',
// SUBMIT_BTN: 'input#submitbtn',
// CITY_INTERVAL: 4,
// SKIP_INTERVAL: 4,
// CYCLES: 4,
// CYCLE_INTERVAL: 4,
CAN_SUBMIT: true,
}
const $ = (str) => document.querySelector(str)
const $$ = (str) => document.querySelectorAll(str)
let RUN = true;
let TIMEOUT = null;
let TOO_MANY_REQUEST_INTERVAL_FN = null;
let PRIMARY_ID = null
// const BOOK_APPOINTMENT = false;
const PRIMARY_ID_SELECTOR = "main+script+script";
const CITY_ID_SELECTOR = "select#post_select option";
const INTERVAL = 1;
const TOO_MANY_REQUEST = 10;
const TOO_MANY_REQUEST_INTERVAL = 45;
const CONTAINER = document.createElement("div");
const CONTAINER_ID = `${Date.now()}_container`;
const TABLE_CONTAINER = document.createElement("div");
const TABLE_CONTAINER_ID = `${Date.now()}_table_container`
const FORM = document.createElement("form");
const FORM_ID = `${Date.now()}_form`
const TABLE = document.createElement("table");
const TABLE_ID = `${Date.now()}_table`
const INTERVAL_DIV = document.createElement("div");
const INTERVAL_DIV_ID = `${Date.now()}_interval_div`
const INTERVAL_DIV_TEXT = document.createElement("p");
const INTERVAL_DIV_TEXT_ID = `${Date.now()}_interval_div_text`
const SCHEDULE_DAYS_URL = "https://www.usvisascheduling.com/en-US/custom-actions/?route=/api/v1/schedule-group/get-family-ofc-schedule-days"
const DEFAULT_OPTIONS = {
headers: {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
method: "POST",
}
const CITY_SCHEDULES = {}
let booked = false
let wait_count = 0
const get_primary_id = () => {
const script_text = document.querySelector(PRIMARY_ID_SELECTOR).textContent;
return script_text.match(/[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}/,)[0];
};
const get_cities = () => {
const options = document.querySelectorAll(CITY_ID_SELECTOR);
const result = [];
for (const option of options) {
option.value.length !== 0 && result.push({ city_name: option.textContent, city_id: option.value });
}
return result;
};
const jsax = async (url, body, success, err) => {
try {
const req = await fetch(url, { ...DEFAULT_OPTIONS, body });
if (req.status == 429) throw new Error("Too many request");
// booked = url === APPOINTMENT_URL
if (req.status == 200) {
console.log(success);
return await req.json();
}
} catch (e) {
console.log(err);
console.log(e);
}
return null;
}
function go_to_start_date(date) {
const [month, year] = [parseInt($(CONSTS.SELECT_MONTH).value), parseInt($(CONSTS.SELECT_YEAR).value)]
const start_date = new Date(`${month + 1}/01/${year}`)
const end_date = new Date(date)
let diff = (end_date.getFullYear() - start_date.getFullYear()) * 12 + end_date.getMonth() - start_date.getMonth()
while (diff !== 0) {
if (diff > 0) {
$(CONSTS.NEXT_BTN).click()
--diff
} else if (diff < 0) {
$(CONSTS.PREV_BTN).click()
++diff
}
}
}
async function looking_for_application(city) {
for (let greenday of [...$$(CONSTS.GREEN_DAY)]) {
greenday.click()
if (!(await waitForElement(CONSTS.TIME_SELECT))) {
break
}
$(CONSTS.TIME_SELECT).click()
if (CONSTS.CAN_SUBMIT) {
booked = true
onClickSubmit()
}
return true
}
return false
}
const check_city = async (city_name, city_id, primary_id, start_date, end_date) => {
try {
let schedule_dates = await jsax(SCHEDULE_DAYS_URL, `parameters={"primaryId":"${primary_id}","applications":["${primary_id}"],"postId":"${city_id}","isReschedule":"false"}`, `Successfully fetched schedule dates for ${city_name} city.`, `Error while fetching schedule dates for ${city_name} city.`)
schedule_dates = schedule_dates?.ScheduleDays || [];
CITY_SCHEDULES[city_name] = schedule_dates
updateTable(CITY_SCHEDULES)
// updateTable(Object.values(CITY_SCHEDULES), Object.keys(CITY_SCHEDULES))
// updateTable(schedule_dates, city_name)
for (let { Date: date } of schedule_dates) {
if (booked || !RUN) break
const format_date = new Date(date);
const is_between = format_date >= start_date && format_date <= end_date;
if (!is_between) continue
$(CONSTS.CITY_SELECT).value = city_id
onPostChange()
await waitForElement(CONSTS.DATE_PICKER)
go_to_start_date(date)
if (await looking_for_application(city_name)) return
console.log(`Found available date for ${city_name} city: ${date}`);
}
} catch (e) {
console.log(`Error while looking up this city: ${city_name}.`);
console.log(e);
}
console.log(`No available date for ${city_name} city.`);
return null;
};
const setDraggable = (element) => {
element.style.position = 'absolute'
element.style.zIndex = 1000
element.style.cursor = 'move'
let dragging = false
let offset = {
x: 0,
y: 0
}
element.addEventListener('mousedown', e => {
dragging = true;
offset.x = e.clientX - element.getBoundingClientRect().left
offset.y = e.clientY - element.getBoundingClientRect().top
})
document.addEventListener('mousemove', e => {
if (dragging) {
element.style.left = `${e.clientX - offset.x}px`
element.style.top = `${e.clientY - offset.y}px`
element.style.cursor = 'grabbing'
}
})
document.addEventListener('mouseup', e => {
dragging = false
element.style.cursor = 'move'
})
}
function createContainer(onsubmit) {
const container = CONTAINER;
container.id = CONTAINER_ID;
container.style.cssText = `
position: fixed;
top: 20px;
left: 20px;
background-color: white;
border: 1px solid #ccc;
padding: 10px;
cursor: move;
z-index: 1000;
`;
const form = FORM;
form.style.display = 'flex';
form.style.flexDirection = 'column';
form.style.gap = '10px';
const startDateContainer = document.createElement('div');
const startDateLabel = document.createElement('label');
startDateLabel.textContent = 'Start Date:';
startDateLabel.htmlFor = 'start-date'; // Ensure the label's for attribute matches the input's id
startDateContainer.appendChild(startDateLabel);
const startDatePicker = document.createElement('input');
startDatePicker.type = 'date';
startDatePicker.id = 'start-date'; // The id should match the label's for attribute
startDatePicker.name = 'start_date';
startDateContainer.appendChild(startDatePicker);
form.appendChild(startDateContainer);
const endDateContainer = document.createElement('div');
const endDateLabel = document.createElement('label');
endDateLabel.textContent = 'End Date:';
endDateLabel.htmlFor = 'end-date'; // Ensure the label's for attribute matches the input's id
endDateContainer.appendChild(endDateLabel);
const endDatePicker = document.createElement('input');
endDatePicker.type = 'date';
endDatePicker.id = 'end-date'; // The id should match the label's for attribute
endDatePicker.name = 'end_date';
endDateContainer.appendChild(endDatePicker);
form.appendChild(endDateContainer);
const citiesContainer = document.createElement('div');
const citiesLabel = document.createElement('label');
citiesLabel.textContent = 'Cities:';
citiesLabel.htmlFor = 'city-picker'; // Ensure the label's for attribute matches the input's id
citiesContainer.appendChild(citiesLabel);
const citySelect = document.createElement('select');
citySelect.id = 'city-picker'; // The id should match the label's for attribute
citySelect.multiple = true;
citySelect.name = 'cities';
citySelect.style.width = '100%';
citiesContainer.appendChild(citySelect);
form.appendChild(citiesContainer);
const cities_list = get_cities();
for (const { city_name, city_id } of cities_list) {
const option = document.createElement("option");
option.textContent = city_name;
option.value = city_id;
citySelect.appendChild(option);
}
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = 'Start';
form.appendChild(submitButton);
const stop_button = document.createElement("button");
stop_button.textContent = "Stop";
stop_button.onclick = (e) => {
e.preventDefault();
RUN = false;
wait_count = 0;
TIMEOUT && clearTimeout(TIMEOUT);
clearInterval(TOO_MANY_REQUEST_INTERVAL_FN);
document.getElementById(INTERVAL_DIV_TEXT_ID) && INTERVAL_DIV.removeChild(INTERVAL_DIV_TEXT);
};
form.id = FORM_ID;
form.onsubmit = onsubmit;
form.appendChild(stop_button);
container.appendChild(form);
document.body.appendChild(container);
}
async function waitForElement(selector) {
return new Promise((resolve) => {
const targetNode = document.body;
const config = { childList: true, subtree: true };
const observer = new MutationObserver((mutationsList, observer) => {
for (let mutation of mutationsList) {
if (mutation.type === "childList") {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
}
}
});
observer.observe(targetNode, config);
});
}
function createTable() {
const container = TABLE_CONTAINER;
container.id = TABLE_CONTAINER_ID;
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background-color: white;
border: 1px solid #ccc;
padding: 10px;
cursor: move;
z-index: 1000;
color: black;
`;
document.body.appendChild(container);
}
function updateTable(schedule_dates) {
TABLE_CONTAINER.innerHTML = '';
const h2 = document.createElement('h2');
h2.textContent = 'Schedule Dates';
h2.style.color = 'black';
TABLE_CONTAINER.appendChild(h2);
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexWrap = 'wrap';
container.style.gap = '10px';
for(const [city_name, dates] of Object.entries(schedule_dates)) {
const p = document.createElement('p');
p.textContent = city_name;
p.style.color = 'black';
p.appendChild(document.createElement('br'))
const ul = document.createElement('ul');
ul.style.listStyleType = 'none';
ul.style.padding = '0';
for(const { Date: date } of dates) {
const li = document.createElement('li');
li.textContent = new Date(date).toLocaleDateString('en-GB');
ul.appendChild(li);
}
p.appendChild(ul);
container.appendChild(p);
}
TABLE_CONTAINER.appendChild(container);
}
async function run(e) {
e.preventDefault();
booked = false
RUN = true
const primary_id = PRIMARY_ID
const cities = Array.from(FORM.cities.selectedOptions).map(option => ({
city_name: option.textContent,
city_id: option.value
}))
const start_date = new Date(FORM.start_date.value);
const end_date = new Date(FORM.end_date.value);
for (const { city_name, city_id } of cities) {
if (booked || !RUN) break
await check_city(city_name, city_id, primary_id, start_date, end_date);
console.log("Count as of now: ", wait_count + 1);
wait_count++;
if (wait_count != 0 && wait_count % TOO_MANY_REQUEST === 0) {
console.log("Too many request :: WAITING TIME....");
const d = INTERVAL_DIV
const p = INTERVAL_DIV_TEXT
p.style.textAlign = 'center'
p.style.fontWeight = 'bold'
p.style.fontSize = '50px'
d.appendChild(p)
document.body.appendChild(d)
let c = TOO_MANY_REQUEST_INTERVAL
TOO_MANY_REQUEST_INTERVAL_FN = setInterval(() => {
p.textContent = `Waiting for ${c} seconds to start the script`
c--
if (c === 0) {
clearInterval(TOO_MANY_REQUEST_INTERVAL_FN)
d.removeChild(p)
}
}, 1000);
await new Promise(resolve => setTimeout(resolve, TOO_MANY_REQUEST_INTERVAL * 1000));
wait_count = 0;
}
}
if (RUN) {
TIMEOUT = setTimeout(() => {
clearTimeout(TIMEOUT)
FORM.dispatchEvent(new Event('submit'))
}, INTERVAL * 1000)
}
}
async function main() {
if (!RUN) return;
console.log("Visa script started");
PRIMARY_ID = get_primary_id();
await waitForElement(CITY_ID_SELECTOR);
createContainer(run)
setDraggable(CONTAINER)
createTable()
setDraggable(TABLE_CONTAINER)
}
window.addEventListener('load', main);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment