Skip to content

Instantly share code, notes, and snippets.

@virgs
Last active June 7, 2024 13:30
Show Gist options
  • Select an option

  • Save virgs/aad19b44440a053fd6410c40b1787e12 to your computer and use it in GitHub Desktop.

Select an option

Save virgs/aad19b44440a053fd6410c40b1787e12 to your computer and use it in GitHub Desktop.
US appointment slot scheduler
const audioSource = 'https://cdn.freesound.org/previews/533/533869_5828667-lq.mp3'
const cityCode = 56; // São Paulo
const formattedMaxDate = '2023-11-30'; // YYYY-MM-DD
const maxNumberOfCandidateDates = 4; //avoid throttling
const language = 'pt-br';
const sessionNumber = 38681568;
const minIntervalBetweenFetchesInMinutes = 2;
const randomizedIntervalBetweenFetchesInMinutes = 2;
const secondsBetweenDateTimeFetchCalls = 10;
const audio = new Audio(audioSource)
const maxDate = new Date(formattedMaxDate).getTime();
function getDatesUrl() {
return `https://ais.usvisa-info.com/${language}/niv/schedule/${sessionNumber}/appointment/days/${cityCode}.json?appointments[expedite]=false`
}
function getDateTimesUrl(availableDay) {
return `https://ais.usvisa-info.com/${language}/niv/schedule/${sessionNumber}/appointment/times/${cityCode}.json?date=${availableDay}&appointments[expedite]=false`
}
let forceStop = false
async function start() {
console.log('Algorithm is running')
forceStop = false
let attempt = 0;
while (!forceStop) {
console.log('Max allowed date: ' + new Date(maxDate).toString());
console.log('Num of attempts: ' + ++attempt)
try {
await search()
} catch (err) {
console.error(err)
}
const waitTime = (minIntervalBetweenFetchesInMinutes + Math.random() * randomizedIntervalBetweenFetchesInMinutes);
console.log('Waiting time: ' + Math.trunc(waitTime) + 'm')
await sleep(waitTime * 1000 * 60)
console.log('-----------------------------')
}
}
function stop() {
forceStop = true
}
async function fetchAvailableDates() {
console.log('Fetching dates...');
const request = new Request(getDatesUrl(), {
method: 'GET', headers: {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9,fr-FR;q=0.8,fr;q=0.7,pt-BR;q=0.6,pt;q=0.5,zh-CN;q=0.4,zh;q=0.3',
'Connection': 'keep-alive',
'X-Requested-With': 'XMLHttpRequest'
}
});
const res = await fetch(request);
const json = await res.json(); //[{"date":"2023-11-27","business_day":true},{"date":"2023-11-28","business_day":true}]
return json
.filter(item => item.business_day);
}
async function fetchAvailableDateTimes(date) {
console.log('Fetching available time slots...');
const request = new Request(getDateTimesUrl(date.toString()), {
method: 'GET', headers: {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9,fr-FR;q=0.8,fr;q=0.7,pt-BR;q=0.6,pt;q=0.5,zh-CN;q=0.4,zh;q=0.3',
'Connection': 'keep-alive',
'X-Requested-With': 'XMLHttpRequest'
}
});
const res = await fetch(request);
const json = await res.json(); //{available_times: ["08:15"], business_times: ["08:15", "08:30", "09:15"]}
return json.available_times
.reduce((acc, item) => json.business_times.includes(item) ? [...acc, item] : acc, []);
}
async function search() {
const availableDates = await fetchAvailableDates();
const availableDatesBeforeScheduledDate = availableDates
.filter(item => new Date(item.date).getTime() < maxDate)
console.log(`Found ${availableDates.length} available dates. ${availableDatesBeforeScheduledDate.length} of them are before scheduled date`);
if (availableDatesBeforeScheduledDate.length > 0) {
console.log(`The first one being on '${availableDatesBeforeScheduledDate[0].date}'. Filtering first ${maxNumberOfCandidateDates} of them to avoid throttling`);
availableDates
.filter((item, index) => index < maxNumberOfCandidateDates)
.forEach(async (item, index) => {
await sleep(secondsBetweenDateTimeFetchCalls * 1000 * index)
checkCandidateDate(item);
});
} else {
console.log('No candidate date was found');
}
}
async function checkCandidateDate(bestDateOfFetch) {
console.log('Candidate day: ' + bestDateOfFetch.date);
console.log('Fetched day is a good candidate. Checking times.');
const availableDateTimes = await fetchAvailableDateTimes(bestDateOfFetch.date)
if (availableDateTimes.length > 0) {
console.log('==================XXXX==================');
console.log('Schedule it on: ' + bestDateOfFetch.date);
console.log('Available times: ' + availableDateTimes.toString());
console.log('==================XXXX==================');
await audio.play();
} else {
console.log('Available day has no available time');
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await start();

What it does

It doesn't reschedule, but it notifies (plays a song) you when a good candidate date is available.

Tutorial

  1. Keep the tab open all the time.
  2. Make sure there's still an available audio here. If it's not the case, find one and replace the value of the variable audioSource.
  3. Go to the us-visa web page and hit Reschedule appointment.
  4. With your browser Network tab open (Hit F12), select the city you want to schedule an appointment and get the cityCode, language and the sessionNumber values. You can find them in Headers -> General -> Request URL. For example, in this url https://ais.usvisa-info.com/en-ca/niv/schedule/41884788/appointment/days/95.json?appointments[expedite]=false, the cityCode would be 95, the language en-ca, and the sessionNumber 41884788.
  5. Replace the value of the variable cityCode with the new value.
  6. Replace the value of the variable language with the new value.
  7. Replace the value of the variable sessionNumber with the new value.
  8. Replace the value of the variable formattedMaxDate with the value you wish.
  9. Copy and paste the content of us-visa-scheduler.js gist file below in the browser Console (Hit F12) tab.
  10. Wait for the audio to play. When it does, run for it (I mean it!) and try to schedule the date (select a different city and then selected the desired city again from the drop list to refresh the calendar).
  11. Repeat the process until you find a good date.

Tips

  1. Based on nothing, but pure empiricism, it seems that Fridays around 17:15 UTC are a good day to find new available dates. Just saying.
@swayamsnigdha
Copy link

Hey this is an extremely useful script, however it stopped working recently with the below error.
image

@virgs
Copy link
Author

virgs commented Apr 13, 2023

Hmm. It's hard to debug from my side. Did you replace the sessionNumber?
I noticed the formattedMaxDate is in the past as well, Mar 2023, I think it should be changed.
If you followed precisely the instructions, I don't discard a bug hidden somewhere there.

@swayamsnigdha
Copy link

Yes I did change the session number and it used to run before a week and recently started seeing this error.

@virgs
Copy link
Author

virgs commented Apr 14, 2023

Hmm. Other than this, the only thing I can think about is the cityCode and the formattedMaxDate. But if it was working and without any changes it's no longer working, I would assume that maybe the server changed something from their side.
Unfortunately, since I got my visa, I don't have the credentials to test it anymore. :/

@swayamsnigdha
Copy link

If you want we can schedule a meeting and debug. getDatesUrl() is returning "https://ais.usvisa-info.com/en-ca/niv/schedule/45240993/appointment/days/94.json?appointments[expedite]=false"
fetch method is trying to call the above API and that api does not exist. In the next line, we are expecting response in array format but as we are getting 404 it's throwing parsing error.

@virgs
Copy link
Author

virgs commented Apr 18, 2023

@dhruvam-thakkar
Copy link

Hey, I am also facing the same error. Seems like a problem with the URL as the us gov might have changed it in the system.
https://ais.usvisa-info.com/en-ca/niv/schedule/45240993/appointment/days/94.json?appointments[expedite]=false
returns a 404 error and when I open it independently it does not exist and says page not found.
So I suspect definitely a change in URL.

@virgs
Copy link
Author

virgs commented Apr 20, 2023

Good. Thanks for your input @dhruvam-thakkar.
Can you provide us the new URL, please? You can check it by opening the Network tab in the DevTools (pressing Control+Shift+J or Command+Option+J (Mac)) and clicking in the calendar icon in the usvisa scheduling page.

@dhruvam-thakkar
Copy link

surprisingly, the url shown on the network tab is the same as the script and has 200 output but gives 404 error when using it via the script.

@swayamsnigdha
Copy link

image

The above two attributes in the headres are the culprit. It's in the original request but not when we are calling the URL through the script

@virgs
Copy link
Author

virgs commented Jul 17, 2023

Sorry for taking so long, I had to have another friend trying to schedule an appointment to be able to debug it properly. As @swayamsnigdha said, adding those header attributes got the job done. Thanks!

@teerth17
Copy link

teerth17 commented Jun 7, 2024

Hi, is this valid for : https://www.usvisascheduling.com/en-US/ofc-schedule/ website?

@virgs
Copy link
Author

virgs commented Jun 7, 2024

Hey @teerth17, I believe so.
But since this gist got way longer than I anticipated, I suggest you use its repo as it's more updated.
Let me know how it goes, please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment