Last active
September 16, 2024 01:04
-
-
Save Ruturaj4/99ee7179700041ffea3e107093c8e598 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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