AFIT Hostel Monitor with Telegram Alerts

Scrape the AFIT portal and get instant Telegram alerts when hostels open.

Introduction

30 Second Summary

Hostel accommodation at AFIT fills up fast. If you're not refreshing the portal every few minutes, you could miss your window entirely.

In this project, you will build a Python bot that logs into your AFIT student portal, watches the accommodation page, and sends you a Telegram alert the instant something changes.

What You'll Build

Picture this: you're in class or asleep, and your phone buzzes with a message from your bot saying new hostel accommodation just appeared on the portal.

By the end of this project, you'll have:

  • A Python scraper that authenticates into the AFIT portal and reads protected pages using session-based HTTP requests.
  • A Telegram bot that delivers instant alerts to your phone whenever the accommodation page changes.
  • A live Railway deployment that runs your monitor 24/7 in the cloud, even with your laptop closed.
  • Secret Mission: Add a daily heartbeat message and network error retry logic so you never miss an alert.

Are there any prerequisites?

You'll need Python 3.10 or higher installed, VS Code, a GitHub account, and a Telegram account on your phone. No prior experience with web scraping or APIs is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters to you. This is the moment you commit to the goal before any hands-on work begins.

Set Up Your Project

You know what you are building and why it matters. Now it is time to create the project foundation.

Before writing any scraping code, you need a clean folder structure, an isolated Python environment, and the right dependencies installed. In this step, you will set all of that up so the rest of the project goes smoothly.

In this step, get ready to:
  • Create a hostel-monitor project folder with a virtual environment.
  • Install the requests library for making HTTP requests.
  • Create the starter files you will build on in later steps.
Create your project folder and open it in VS Code

Every project starts with a dedicated folder. Creating one on your Desktop makes it easy to find later.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir hostel-monitor
cd hostel-monitor

Now open this folder in VS Code.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the hostel-monitor folder, and click Open.

You should see hostel-monitor in the VS Code title bar and an empty Explorer panel on the left.

  • Open the integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).

What is the integrated terminal?

VS Code has a built-in terminal so you can run commands without leaving the editor. It opens directly inside your project folder, which means every command you run already points at hostel-monitor.

Set up a virtual environment and install requests

A virtual environment is an isolated copy of Python just for this project. It keeps your dependencies separate from other projects on your machine.

  • Create a virtual environment by running this command in the VS Code terminal:
python -m venv venv

What does this command do?

python -m venv venv tells Python to create a virtual environment in a folder called venv. You should see a new venv/ folder appear in your Explorer panel on the left.

Now activate the virtual environment so that any packages you install go into it instead of your system Python.

macOS

  • Activate the virtual environment by running:
source venv/bin/activate

This same command works on Linux.

Windows

  • Activate the virtual environment by running:
venv\Scripts\activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Windows: seeing a script execution error?

If you see an error about running scripts being disabled, run this command first to allow local scripts:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Then try the activate command again. Help me fix my virtual environment activation error.

  • Install the requests library by running:
pip install requests==2.34.2

Why pin the version?

Pinning to requests==2.34.2 ensures everyone running this project gets the exact same behaviour. Without a pin, a future version could introduce breaking changes.

Before you run the next command, what information do you expect to see about the installed package?

  • Verify the installation by running:
pip show requests

You should see output that includes Version: 2.34.2 along with the package name, summary, and install location.

Not seeing version 2.34.2?

Make sure your virtual environment is active. You should see (venv) in your terminal prompt. If not, run the activate command from above again, then re-run the install.

Help me troubleshoot my requests installation.

Create your project files

Your project needs three files. You will fill them in over the next steps, but creating them now gives you a clear structure to work with.

  • In the VS Code Explorer panel, hover over the HOSTEL-MONITOR heading and click the New File icon (a page with a plus sign).
  • Type requirements.txt and press Enter.
  • Paste this single line into the file:
requests==2.34.2

What is requirements.txt?

This file tells anyone (or any server) running your project exactly which packages to install. When you deploy later, Railway reads this file to set up your dependencies automatically.

  • Save requirements.txt with Cmd+S (macOS) or Ctrl+S (Windows).
  • Click the New File icon again. Type main.py and press Enter.
  • Click the New File icon one more time. Type .env and press Enter.

What is the .env file for?

The .env file is where you will store sensitive values like your portal login and Telegram bot token during local testing. It never gets pushed to GitHub, keeping your credentials safe.

  • Confirm all three files exist by running this in your terminal:
ls

You should see main.py, requirements.txt, .env, and the venv folder listed in the output.

Don't see .env in the output?

Files starting with a dot are hidden by default on macOS/Linux. Run ls -a instead to show all files including hidden ones. On Windows PowerShell, ls shows all files by default.

Help me find my .env file.

✔️ Awesome, I've got everything!

Great. Double check you have saved requirements.txt and you are good to go.

ⓧ I'd like to double check the full code

Your requirements.txt should contain exactly this:

requests==2.34.2

Your main.py and .env files should both be empty with no content inside them.

Your project structure is locked in. You have an isolated environment, the right library installed, and three files ready to build on. Next up, you will write the code that logs into the AFIT portal and reads the accommodation page.

Build the Portal Scraper

Your project folder is ready with all the dependencies installed. Now for the exciting part: writing code that can log into your AFIT portal and read the hostel accommodation page.

Before you can detect changes, you need to prove your script can authenticate and read the page. This is the core skill of session-based web scraping. You will use browser DevTools to reverse-engineer the login form, then replicate that login in Python.

In this step, get ready to:
  • Inspect the AFIT portal login form to find the exact field names your script needs.
  • Write a Python function that logs in and scrapes the accommodation page.
  • Run the script and confirm it prints the current page content.
Inspect the login form to find field names

When your script submits a login form, it needs to send data using the exact same field names the form expects. If the form expects a field called jamb_no but your script sends username, the login will silently fail.

  • Open your browser and navigate to https://portal.afit.edu.ng/student/.
  • Right-click on the Jamb No input field.
  • Select Inspect from the context menu. This opens DevTools with the input element highlighted.
  • In the Elements panel, look at the highlighted <input> tag and find its name attribute. Note this value down.
  • Repeat the same process for the Password field. Right-click it, select Inspect, and note its name attribute.

Why do you need these field names?

HTML forms submit data as key-value pairs. The keys are the name attributes on each input field. When your Python script POSTs to the login URL, it must use the same keys the server expects.

For the AFIT portal, you should find the field names are jamb_no and password. Confirm these match what you see in DevTools before continuing.

Write the login and scrape function

Now you know the field names, it is time to write the Python code. Your script will use an HTTP session to log in and then read the protected accommodation page.

  • Switch back to VS Code and open main.py.
  • Add the imports and configuration constants by pasting this code:
import os
import time

import requests

# Read login credentials from environment variables
JAMB_NO = os.environ["JAMB_NO"]
PORTAL_PASSWORD = os.environ["PORTAL_PASSWORD"]

# Portal URLs and check interval
PORTAL_LOGIN_URL = "https://portal.afit.edu.ng/student/"
ACCOM_URL = "https://portal.afit.edu.ng/student/pay_acom_now"
CHECK_INTERVAL = 180

What does this code do?

  • The first three lines import the libraries you need: os for reading environment variables, time for delays later, and requests for making HTTP requests.
  • The os.environ["JAMB_NO"] lines pull your credentials from environment variables instead of hardcoding them in the file. This keeps your password out of your source code.
  • The URL constants tell your script where to send the login and where to fetch the accommodation page.
  • Below the constants, add the scraping function and the entry point by pasting this code:
def check_portal():
    # Create a session to persist cookies across requests
    session = requests.Session()
    login_data = {
        "jamb_no": JAMB_NO,
        "password": PORTAL_PASSWORD,
    }
    # Log in by POSTing credentials to the login page
    session.post(PORTAL_LOGIN_URL, data=login_data)
    # Fetch the accommodation page with the authenticated session
    response = session.get(ACCOM_URL)
    return response.text


if __name__ == "__main__":
    print(check_portal())

What does this code do?

  • The requests.Session() creates a persistent session that remembers cookies. When you POST your login, the server sets a session cookie that proves you are authenticated.
  • The same session object carries that cookie into the GET request, so the server knows you are logged in when you request the accommodation page.
  • The if __name__ == "__main__" block means the code inside only runs when you execute the file directly. It calls check_portal() and prints whatever the accommodation page contains.
  • Save main.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before continuing.

ⓧ I'd like to double check the full code

Your complete main.py should look exactly like this:

import os
import time

import requests

JAMB_NO = os.environ["JAMB_NO"]
PORTAL_PASSWORD = os.environ["PORTAL_PASSWORD"]

PORTAL_LOGIN_URL = "https://portal.afit.edu.ng/student/"
ACCOM_URL = "https://portal.afit.edu.ng/student/pay_acom_now"
CHECK_INTERVAL = 180


def check_portal():
    session = requests.Session()
    login_data = {
        "jamb_no": JAMB_NO,
        "password": PORTAL_PASSWORD,
    }
    session.post(PORTAL_LOGIN_URL, data=login_data)
    response = session.get(ACCOM_URL)
    return response.text


if __name__ == "__main__":
    print(check_portal())
Set environment variables and run the script

Your script reads credentials from environment variables. You need to set these in your terminal before running the script so it can log in.

  • Set your JAMB number and portal password as environment variables by running these commands in your VS Code terminal:

macOS

export JAMB_NO=your-jamb-number-here
export PORTAL_PASSWORD=your-portal-password-here

This syntax also works on Linux.

Windows

$env:JAMB_NO = "your-jamb-number-here"
$env:PORTAL_PASSWORD = "your-portal-password-here"
  • Replace your-jamb-number-here with your actual JAMB number.
  • Replace your-portal-password-here with your actual portal password.

Why environment variables instead of hardcoding?

Hardcoding your JAMB number and password directly in your Python file means anyone who sees your code also sees your credentials. Environment variables keep secrets outside your source code.

Later when you deploy to Railway, you will set these same variables in the cloud dashboard instead of your terminal.

Before you run the script, what do you expect to see printed? Think about what the accommodation page currently shows for last session.

  • Run your script by executing this command:
python main.py

You should see a wall of HTML text printed to your terminal. Look through it for the phrase "payment successfully". This confirms your script logged in and read the accommodation page.

Not seeing any output or getting an error?

  • If you see KeyError: 'JAMB_NO', your environment variables are not set. Run the export (macOS) or $env: (Windows) commands again in the same terminal window.
  • If the output is HTML but does not contain "payment successfully", double-check your JAMB number and password are correct. Also confirm the field names in your code match what you found in DevTools.
  • If you see a connection error, check your internet connection and confirm you can open https://portal.afit.edu.ng/student/ in your browser.

Help me debug my AFIT portal scraper

Your script can now log into the AFIT portal and read the accommodation page. Next up, you will connect a Telegram bot so your phone buzzes the instant this page changes.

Integrate Telegram Alerts

Your script can now log into the AFIT portal and read the accommodation page. In this step, you'll connect it to Telegram so alerts hit your phone instantly.

Right now, the script only prints to your terminal. If your laptop is closed or you're away from your desk, you'll miss the moment hostels open. Telegram's Bot API lets you send messages programmatically with a simple HTTP request.

In this step, get ready to:
  • Create a Telegram bot using @BotFather.
  • Retrieve your personal chat ID from the Telegram API.
  • Write the alert function and send a test message to your phone.
Create a Telegram bot via BotFather

Telegram lets anyone create bots that send and receive messages. You'll create one now using @BotFather, Telegram's official bot-management tool.

  • Open Telegram on your phone or desktop app.
  • In the search bar at the top, type @BotFather and tap the verified result (it has a blue checkmark).
  • Tap Start at the bottom of the chat if prompted.
  • Type /newbot and send the message.
  • BotFather will ask for a display name. Type any name you like (e.g. AFIT Hostel Monitor).
  • BotFather will then ask for a username. This must end in bot and be 5-32 characters (e.g. afit_hostel_monitor_bot).
  • BotFather replies with your bot token. It looks like 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw. Copy this token and save it somewhere safe.

What is a bot token?

The token is your bot's password. Anyone with this token can control your bot, so never share it publicly or commit it to Git.

  • Now open a chat with your new bot. Search for the username you just created in Telegram's search bar.
  • Tap Start to activate the conversation. This allows your bot to send messages back to you.
Get your chat ID

Your bot needs to know where to send messages. Every Telegram user has a unique numeric chat ID that identifies their conversation with the bot.

  • Open your web browser and navigate to this URL, replacing YOUR_TOKEN with the bot token you copied:
https://api.telegram.org/botYOUR_TOKEN/getUpdates
  • You'll see a JSON response in your browser. Look for the "chat":{"id": section.
  • Copy the number after "id": (e.g. 123456789). This is your chat ID.

Seeing an empty result array?

If the JSON shows "result":[] with nothing inside, go back to Telegram and send any message (like hello) to your bot. Then refresh the browser page. Your message should now appear in the response with your chat ID.

Still stuck? Help me find my Telegram chat ID from the getUpdates response.

Write the alert function and send a test message

You now have a bot token and a chat ID. Time to wire them into your script so it can send alerts.

  • Set your Telegram credentials as environment variables in your terminal by running these commands (replace the placeholder values with your actual token and chat ID):

macOS

export TELEGRAM_BOT_TOKEN=your-bot-token-here
export TELEGRAM_CHAT_ID=your-chat-id-here

Windows

set TELEGRAM_BOT_TOKEN=your-bot-token-here
set TELEGRAM_CHAT_ID=your-chat-id-here

Why set these in the terminal?

Environment variables keep secrets out of your code. Your script reads them at runtime, so your token never appears in a file you might accidentally push to GitHub.

  • In VS Code, open main.py. Find these two lines near the top:
JAMB_NO = os.environ["JAMB_NO"]
PORTAL_PASSWORD = os.environ["PORTAL_PASSWORD"]
  • Add two new lines directly below them to load your Telegram credentials:
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
  • Now find the check_portal() function definition. Add the following new function directly above it:
def send_telegram_alert(message):
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {"chat_id": TELEGRAM_CHAT_ID, "text": message}
    response = requests.post(url, json=payload)
    return response.json()

What does this function do?

The function builds the Telegram API URL using your bot token, then sends a POST request with your chat ID and the message text. Telegram delivers the message to your phone instantly.

It returns the JSON response from Telegram so you can confirm the message was accepted.

  • Scroll to the bottom of main.py. Find this block:
if __name__ == "__main__":
    print(check_portal())
  • Add a test alert call below print(check_portal()) so the block looks like this:
if __name__ == "__main__":
    print(check_portal())
    send_telegram_alert("Test message from AFIT Hostel Monitor!")
  • Save main.py.

Before you run this, what do you think will happen? Will your phone buzz with a message, or will you see an error?

  • Run the script by entering this command:
python main.py

You should see the portal page content printed to your terminal, followed by a Telegram notification on your phone with the text "Test message from AFIT Hostel Monitor!"

No message on your phone?

  • Check that you sent /start to your bot in Telegram before running the script. Without this, the bot cannot message you.
  • Verify your TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID environment variables are set correctly. Run echo %TELEGRAM_CHAT_ID% (Windows) or echo $TELEGRAM_CHAT_ID (macOS) to confirm.
  • Make sure the chat ID is just the number (e.g. 123456789) with no extra characters or quotes.

Help me debug why my Telegram bot isn't sending messages.

✔️ Awesome, I've got everything!

Great. Make sure you've saved main.py and confirmed the test message arrived on your phone.

ⓧ I'd like to double check the full code

Your main.py should have these sections in order. Scroll up and confirm each one is present:

  • Imports (os, time, requests).
  • Four environment variable constants: JAMB_NO, PORTAL_PASSWORD, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID.
  • Three URL/interval constants: PORTAL_LOGIN_URL, ACCOM_URL, CHECK_INTERVAL.
  • The send_telegram_alert(message) function that POSTs to the Telegram API.
  • The check_portal() function that logs in and returns page text.
  • The if __name__ == "__main__": block calling both print(check_portal()) and send_telegram_alert("Test message from AFIT Hostel Monitor!").

Your bot is alive and talking. Next up, you'll wrap everything in a monitoring loop that checks the portal every 3 minutes and only alerts you when the page actually changes.

Add the Monitoring Loop

Your script can log in and read the portal. Your Telegram bot can send messages to your phone. The missing piece is automation.

A single check is useless because it only captures a snapshot. You need the script to check repeatedly and only alert you when something actually changes. Without state tracking, the bot would either spam you every 3 minutes or miss the moment hostels open entirely.

In this step, get ready to:
  • Add a monitoring loop that checks the portal every 3 minutes.
  • Add change detection that only alerts on status transitions.
  • Add error handling so the script never crashes and stops watching.
Add the main monitoring loop

Right now your if __name__ block runs once and exits. You need a main() function that loops forever, checking the portal on a fixed interval and remembering the last status it saw.

  • In main.py, find the existing if __name__ == "__main__": block at the bottom of the file.
  • Delete the entire block (the if __name__ line and the two lines below it).
  • Replace it with the following main() function. Copy this code below your check_portal() function:
def main():
    print("Starting AFIT Hostel Monitor...")
    print(f"Checking every {CHECK_INTERVAL} seconds.")
    last_status = "unknown"

    while True:
        try:
            page_content = check_portal()
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S")

            if "payment successfully" in page_content.lower():
                current_status = "old_session"
                print(f"[{timestamp}] No change - still showing last session.")
            else:
                current_status = "changed"
                print(f"[{timestamp}] PAGE CHANGED! New content detected.")

                if last_status != "changed":
                    send_telegram_alert(
                        "AFIT HOSTEL ALERT!\n\n"
                        "The accommodation page has changed!\n"
                        "New session hostels may be available.\n\n"
                        "Check now: https://portal.afit.edu.ng/student/pay_acom_now"
                    )
                    print(f"[{timestamp}] Telegram alert sent!")

            last_status = current_status

What does this code do?

  • last_status = "unknown" remembers what the script saw on its previous check. This is how you track whether anything changed.
  • while True: creates an infinite loop so the script runs until you manually stop it.
  • time.strftime("%Y-%m-%d %H:%M:%S") generates a human-readable timestamp like 2026-08-05 14:30:45 for your log lines.
  • The if "payment successfully" in page_content.lower() check detects whether the page still shows last session's content.
  • The if last_status != "changed" guard ensures a Telegram alert only fires on the first transition. Without it, you would get a new alert every 3 minutes while the page stays in the "changed" state.
Add error handling and close the loop

The try: block you started needs an except to catch network errors. Without it, a single timeout would crash the script and stop monitoring entirely.

  • Directly below the last_status = current_status line you just added, paste the error handler and sleep:
        except Exception as e:
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
            print(f"[{timestamp}] Error: {e}")

        time.sleep(CHECK_INTERVAL)


if __name__ == "__main__":
    main()

What does this code do?

  • except Exception as e: catches any error (network timeout, connection refused, etc.) and logs it instead of crashing.
  • time.sleep(CHECK_INTERVAL) pauses for 180 seconds (3 minutes) between each check cycle, regardless of whether the check succeeded or failed.
  • if __name__ == "__main__": main() is the new entry point that kicks off the monitoring loop when you run the script.
Understand the change detection logic

Why does last_status prevent repeated alerts?

The portal currently shows "payment successfully" from last session. Your script marks this as old_session. When the university updates the page with new hostel information, the text changes and the status flips to changed.

The alert only fires when last_status is NOT already changed. After the first alert, last_status becomes changed, so subsequent loops see no transition and skip the alert. You get one notification, not one every 3 minutes.

  • Save main.py.

Seeing a syntax error?

  • Make sure the except block is indented to the same level as the try: above it (two levels of indentation inside main()).
  • Check that time.sleep(CHECK_INTERVAL) is at the same indent level as try: and except: (inside the while True but outside the try/except).
  • Confirm you deleted the old if __name__ block completely. Having two of them causes unexpected behaviour.

help me fix a syntax error in my Python monitoring loop

Before you run this, what do you expect to see in your terminal? Will it print once and exit, or keep printing?

  • Make sure your environment variables are set in your terminal (the same ones from the previous steps).
  • Run the script by entering this command:
python main.py

You should see two startup lines followed by a timestamped log entry like this:

Starting AFIT Hostel Monitor...
Checking every 180 seconds.
[2026-08-05 14:30:45] No change - still showing last session.

The script then waits 3 minutes before checking again. Let it run for at least 6 minutes (2 cycles) to confirm it keeps logging without spamming your phone.

How do I stop the script?

Press Ctrl+C in your terminal to stop the loop. This is how you exit any long-running Python script.

Not seeing any output?

  • Check that your environment variables (JAMB_NO, PORTAL_PASSWORD, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID) are still set. If you opened a new terminal, you need to re-export them.
  • If you see KeyError: 'JAMB_NO', the environment variable is missing. Set it again before running.
  • If the script seems stuck with no output at all, it may be waiting on a slow network response. Wait 30 seconds. If still nothing, press Ctrl+C and check your internet connection.

help me troubleshoot my AFIT hostel monitor not producing output

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and confirmed the loop runs with timestamped log lines.

ⓧ I'd like to double check the full code

Here is the complete main.py at this point. Compare it with yours:

import os
import time

import requests

JAMB_NO = os.environ["JAMB_NO"]
PORTAL_PASSWORD = os.environ["PORTAL_PASSWORD"]
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
PORTAL_LOGIN_URL = "https://portal.afit.edu.ng/student/"
ACCOM_URL = "https://portal.afit.edu.ng/student/pay_acom_now"
CHECK_INTERVAL = 180


def send_telegram_alert(message):
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {"chat_id": TELEGRAM_CHAT_ID, "text": message}
    response = requests.post(url, json=payload)
    return response.json()
def check_portal():
    session = requests.Session()
    login_data = {
        "jamb_no": JAMB_NO,
        "password": PORTAL_PASSWORD,
    }
    session.post(PORTAL_LOGIN_URL, data=login_data)
    response = session.get(ACCOM_URL)
    return response.text
def main():
    print("Starting AFIT Hostel Monitor...")
    print(f"Checking every {CHECK_INTERVAL} seconds.")
    last_status = "unknown"

    while True:
        try:
            page_content = check_portal()
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S")

            if "payment successfully" in page_content.lower():
                current_status = "old_session"
                print(f"[{timestamp}] No change - still showing last session.")
            else:
                current_status = "changed"
                print(f"[{timestamp}] PAGE CHANGED! New content detected.")

                if last_status != "changed":
                    send_telegram_alert(
                        "AFIT HOSTEL ALERT!\n\n"
                        "The accommodation page has changed!\n"
                        "New session hostels may be available.\n\n"
                        "Check now: https://portal.afit.edu.ng/student/pay_acom_now"
                    )
                    print(f"[{timestamp}] Telegram alert sent!")

            last_status = current_status
        except Exception as e:
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
            print(f"[{timestamp}] Error: {e}")

        time.sleep(CHECK_INTERVAL)


if __name__ == "__main__":
    main()

Your monitor now runs continuously, detects changes, and only alerts once per transition. Next up, you will deploy this to Railway so it keeps watching even when your laptop is closed.

Deploy to Railway

Your monitor works perfectly on your machine. But the moment you close your laptop or lose internet, the script stops and you miss the alert.

Railway is a cloud platform that runs your script 24/7 without needing your computer. In this step, you'll push your code to GitHub and deploy it to Railway so the monitor never sleeps.

In this step, get ready to:
  • Push your code to a GitHub repository.
  • Deploy the script to Railway from your GitHub repo.
  • Set environment variables in Railway so your script can authenticate.
Create a .gitignore and push to GitHub

Before pushing your code, you need to tell Git which files to ignore. Your virtual environment folder and .env file with credentials should never be uploaded to GitHub.

  • In VS Code, create a new file called .gitignore in your hostel-monitor folder.
  • Add the following content to .gitignore:
venv/
.env
__pycache__/

What does this file do?

Git reads .gitignore to decide which files to skip when tracking changes. Each line is a pattern that Git will never commit.

  • venv/ keeps your large virtual environment folder out of the repo.
  • .env prevents your credentials from being uploaded to GitHub.
  • __pycache__/ excludes Python's compiled bytecode files.
  • Save .gitignore.

Now initialize a Git repository and make your first commit. Open the terminal in VS Code.

  • Initialize a Git repo and commit your files by running these commands:
git init
git add .
git commit -m "initial commit"

You should see output confirming that files were committed, including main.py, requirements.txt, and .gitignore.

Seeing 'not a git command' or 'git not found'?

Make sure Git is installed on your machine. Run git --version to check. If it is not installed, download it from git-scm.com.

Help me install Git on my machine.

Next, create a repository on GitHub to store your code remotely.

  • Open your browser and go to github.com.
  • Click the + icon in the top right, then click New repository.
  • Enter hostel-monitor as the repository name.
  • Leave all other settings as default (Public is fine).
  • Click Create repository.
  • GitHub will show you a page with setup commands. Copy the HTTPS URL for your new repo (it looks like https://github.com/YOUR_USERNAME/hostel-monitor.git).
  • Back in your VS Code terminal, push your code to GitHub by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/hostel-monitor.git"]]
git branch -M main
git push -u origin main

What do these commands do?

  • git remote add origin links your local repo to the GitHub repository.
  • git branch -M main renames your default branch to main.
  • git push -u origin main uploads your code to GitHub.
  • Refresh the GitHub page in your browser. You should see main.py, requirements.txt, and .gitignore listed in your repository.

Push failed or asking for credentials?

GitHub may prompt you to authenticate. If you see a browser popup asking you to sign in to GitHub, complete the sign-in and try the push command again.

If you see a permission denied error, make sure the repository URL matches exactly what GitHub showed you on the new repository page.

Help me push my code to GitHub.

Deploy to Railway

Your code is on GitHub. Now you'll connect it to Railway so it runs in the cloud 24/7.

  • Open railway.com in your browser.
  • Click Login and choose GitHub to sign up or sign in with your GitHub account.
  • Once logged in, click New Project.
  • Choose the GitHub repo option.
  • Search for hostel-monitor and click on it.
  • Click Deploy Now.

What happens when you click Deploy Now?

Railway creates a new project and immediately starts building your code. It auto-detects Python from your requirements.txt and uses python main.py as the start command. No Dockerfile needed.

The first deploy will fail because your environment variables are not set yet. That is expected. You will fix it in the next substep.

After clicking Deploy Now, you land on the Project Canvas. You should see your service tile with a build in progress (or a failed deploy).

Don't see your repository in the list?

Railway needs permission to access your GitHub repos. If prompted, click Configure GitHub App and grant access to the hostel-monitor repository. Then try again.

Help me connect my GitHub repository to Railway.

Set environment variables in Railway

Your script needs four credentials to run. On your local machine these came from your terminal session. On Railway, you set them in the dashboard.

  • In the Railway Project Canvas, click on your service tile (it shows the name of your repo).
  • Click the Variables tab.
  • Click New Variable and add the first variable with name JAMB_NO and your JAMB number as the value.
  • Click New Variable again and add PORTAL_PASSWORD with your portal password as the value.
  • Add a third variable: TELEGRAM_BOT_TOKEN with your bot token as the value.
  • Add a fourth variable: TELEGRAM_CHAT_ID with your chat ID as the value.

Where do I find these values?

These are the same values you used to run the script locally. Your JAMB number and portal password are your AFIT student credentials. Your bot token came from BotFather, and your chat ID came from the getUpdates API call in the previous step.

After you add all four variables, Railway automatically redeploys your service with the new credentials.

  • Wait about 30 seconds for the redeploy to finish building.
  • Click View logs on the latest deployment to open the log viewer.

Before you look at the logs, what do you expect to see if the monitor is running correctly?

You should see Starting AFIT Hostel Monitor... followed by timestamped log lines like [2026-08-05 14:30:00] No change - still showing last session. appearing every 3 minutes.

Seeing a crash or 'KeyError' in the logs?

A KeyError means one of your environment variables is missing or misspelled. Go back to the Variables tab and double-check that all four variable names match exactly: JAMB_NO, PORTAL_PASSWORD, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID.

Help me fix my Railway deployment.

Your monitor is now live in the cloud. It will check the AFIT portal every 3 minutes, day and night, even with your laptop off. The moment the accommodation page changes, your phone will buzz with a Telegram alert.

Secret mission

Add a Heartbeat and Retry Logic

Your bot is running 24/7, but how do you know it hasn't silently crashed? What if a network blip kills a check cycle? Add a daily heartbeat message and exponential backoff retry logic so you can trust your bot is always watching.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs on Railway's free trial ($5 credit for 30 days), so there are no immediate costs while your credit lasts.

Resources you used:

  • Railway service running the hostel-monitor script (free trial, $5 credit for 30 days).
  • Telegram bot (free, no ongoing cost).
  • Local hostel-monitor folder on your Desktop.
  • GitHub repository.

Keep everything running

No action needed. Choose this if you are still waiting for the hostel alert. Your $5 Railway trial credit covers roughly 30 days of continuous operation for this tiny script.

  • Leave the Railway service running. It checks the portal every 3 minutes and sends you a Telegram alert the moment hostels become available.
  • Your Telegram bot stays active and free.
  • Your local hostel-monitor folder and GitHub repo remain on your machine for future edits.

Pause - I'll come back to this later

Stop the Railway service to preserve your free credit. You can redeploy anytime by pushing a new commit or restarting the service.

  • Open the Railway dashboard and click on your hostel-monitor project.
  • Click Settings at the top right of the project canvas.
  • Select the Danger tab on the left side of the settings page.
  • Click Delete Project to stop execution. No charges accrue when the project is removed.
  • Your GitHub repo and local files remain untouched for when you want to redeploy.

Delete - I don't want to use this again

Remove all project resources permanently. This cannot be undone.

  • Delete the Railway project: open the project in the Railway dashboard, click Settings at the top right of the project canvas, select the Danger tab, then click Delete Project.
  • Revoke your Telegram bot token: open Telegram, message @BotFather, send /revoke, and select your bot. This invalidates the token so nobody else can use it.
  • Delete the GitHub repository: go to your repo on GitHub, click Settings, scroll to the Danger Zone section at the bottom, and click Delete this repository.
  • Delete the local project folder. On macOS or Linux, run:
rm -rf ~/Desktop/hostel-monitor
  • On Windows, run:
Remove-Item -Recurse -Force ~\Desktop\hostel-monitor

Nice Work!

Nice Work!

You just built a cloud-hosted monitoring bot that watches the AFIT student portal around the clock and alerts your phone the instant hostel accommodation becomes available.

You've learned how to:

  • Build a Python web scraper that authenticates into a protected portal using session-based requests and reads pages behind a login wall.
  • Integrate the Telegram Bot API to send instant alerts to your phone using raw HTTP requests.
  • Deploy a 24/7 cloud service to Railway with a monitoring loop that tracks state and only alerts on actual changes.
  • Secret Mission: Added a daily heartbeat message and exponential backoff retry logic for production resilience.

Ready to quiz yourself?