Reverse engineering an easy way to check GNIB appointments

Using reverse engineering to easily pull appointments from the GNIB website
published: (updated: )
by Harshvardhan J. Pandit
is part of: GNIB Appointments
GNIB reverse-engineering visa

source: hosted on Github here

webapp: hosted on Heroku here shows available timings for GNIB and Visa appointments

As per the the Irish Law I must register myself with the local Police (or Garda). They issue a GNIB card, which is proof that I am registered with them and am permitted to remain in Ireland. For this, I need to take an appointment online through their website. Seems like a little extra effort but then that much was enough to make me stop working on what I was supposed to be doing and instead look into how I can save time on these appointment bookings.

The caveat with this approach is that now everyone books online, and somehow there almost never are any free appointments available. To just check whether there are any appointments available (in a future date), I need to full a form with 15 fields, which itself takes 5 minutes, only to see a "no appointments available" message at the end. Frustrating.

Through this post, I aim to document my efforts into reverse engineering GNIB appointment website and protocols to easily get available appointments. It resulted in a bit of javascript code that when pasted into the browser console, prints the available appointments. This is intended to be a first of many posts detailing my efforts to make this a side-project and a tool that can be helpful for others.

Digging through javascript - Form onClick

The first rule of automation is to familiar oneself with what is being automated. So I set out to inspect what happens when I fill the form and hit the get appointments button. This involved digging through the javascript the site runs and following variables and AJAX requests around. Thankfully, Chrome makes this really easy using right click -> inspect.

The button (id btLook4App) has an onclick value of allowLook4App() defined in Appform.js. The function calls another function called get4DateAvailability() which is where the appointments are retrieved using an AJAX request. The url for this request is constructed dynamically using the statement "/" + stPath + "/(getApps4DTAvailability)?openpage" where stPath is "Website/AMSREG/AMSRegWeb.nsf". One thing to note here is the addition of openpage at the end of the URL - it is meant to be an empty GET parameter. Looking at the other parameters of the AJAX request, one can figure out that it only sends information about

  1. Category

Yup, that's all the information it sends from those 15 fields that were filled. The whole requirement of needing the entire form to be filled and validated before sending requests is a sham (and a shame).

GET request

The GET parameters can be summarised as:

{
    "cat": "value of element #Category",
    "sbcat": "All",
    "typ: "Renewal",
    "openpage": ""
}

The kicker here is that the URL handling this GET request has (sanely) been configured to implement CORS which means that I cannot just query it from any other website. So I opened the browser console and tried to see if my AJAX request worked by basically copying a minimal version of the original. It needs a variable called dataThis which is based on Category and a static string.

var dataThis = "&cat=Study&sbcat=All&typ=Renewal";
$.ajax( {
    type : "GET",
    url :   "/" + stPath + "/(getApps4DTAvailability)?openpage",
    data : dataThis,
    success : function(data) {
        console.log(data);
    }
});

which yields the JSON response of Object {slots: "[]"} which is NOTHING.

AJAX request and response

So I knew I was on a wrong track. Further diggging through requests gave the new URL of "/" + stPath + "/(getAppsNear)?openpage" in function getEarliestApps() which is part of a hidden button that gets activated/shown only after the form is complete and validated. Querying this new URL using the following function yielded in the given response.

var dataThis = "&cat=Study&sbcat=All&typ=Renewal";
$.ajax( {
    type : "GET",
    url :   "/" + stPath + "/(getAppsNear)?openpage",
    data : dataThis,
    success : function(data) {
        console.log(data);
    }
});
{
    "slots": [
        {
            "id": "0A03221D91CF90848025811400317D02",
            "time": "4 July 2017 - 10:00"
        },
        {
            "id": "3B7F286B73A976F08025812500318633",
            "time": "21 July 2017 - 08:00"
        }
    ]
}

On a successful query, the response sends over available times in a list under the key slots comprised of id and time, both of which are strings. For my purpose, it was enough to extract the time values as available appointments. Studying getEarliestApps() shows the conditions and ways to detect other possible conditions.

  • If there is a field called data.error then the request has an error
  • If there is a field called data.empty then there are no appointments currently available
  • Just to be sure, I also check length of data.slots as it can be an empty list

A quick way to get appointments

This gave me a way to quickly check available appointments by opening the website and executing the AJAX request with the success function printing out the available appointment times. Any error on the console means there was an error with the request or there are no appointments available currently.

var dataThis = "&cat=Study&sbcat=All&typ=Renewal";
$.ajax( {
    type : "GET",
    url :   "/" + stPath + "/(getAppsNear)?openpage",
    data : dataThis,
    success : function(data) {
        for(appointment of data.slots) {
            console.log(appointment.time);
        }
    }
});

Future plans

  • Turn this into a script that I can run anywhere (terminal/server) so that it can be automated (cron)
  • Create a chrome extension that does the AJAX request automatically periodically and then notifies using an icon the available appointments