commit ca82885fb776759c419f3e1463160417c896eead Author: Valentijn Date: Wed Mar 18 19:49:14 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b95af1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +shifts.db \ No newline at end of file diff --git a/API.py b/API.py new file mode 100644 index 0000000..e69de29 diff --git a/DB.py b/DB.py new file mode 100644 index 0000000..429328b --- /dev/null +++ b/DB.py @@ -0,0 +1,52 @@ +import sqlite3 + + +class Database: + def __init__(self): + self._con = sqlite3.connect("shifts.db") + self._setup_tables() + pass + + + def _setup_tables(self): + cur = self._con.cursor() + cur.execute(''' + CREATE TABLE IF NOT EXISTS shifts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + shift_start DATETIME NOT NULL, + shift_end DATETIME NOT NULL, + department TEXT NOT NULL, + duration TEXT NOT NULL, + description TEXT, + fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(shift_start, department) -- No duplicates + ); + ''') + self._con.commit() + + + def insert_shifts(self, shifts): + cur = self._con.cursor() + inserted = 0 + + for shift in shifts: + start = shift.get('start_datetime', 'N/A') + end = shift.get('end_datetime', 'N/A') + + department = shift.get('department_name', 'N/A') + duration = shift.get('duration', 'N/A') + description = shift.get('shift_remark', shift.get('description', 'N/A')) + + cur.execute(""" + INSERT OR IGNORE INTO shifts + (shift_start, shift_end, department, duration, description) + VALUES (?, ?, ?, ?, ?) + """, (start, end, department, duration, description)) + + if cur.rowcount > 0: + inserted += 1 + + self._con.commit() + print(f"āœ… Inserted {inserted}/{len(shifts)} new shifts") + diff --git a/PMT.py b/PMT.py new file mode 100644 index 0000000..4f748ad --- /dev/null +++ b/PMT.py @@ -0,0 +1,127 @@ +import os +import requests +import json +from datetime import datetime, timedelta +import sys + +class PMT: + def __init__(self): + self.session = None + self.context_token = None + self.user_token = None + + self._username = os.environ.get("USERNAME_PMT") + self._password = os.environ.get("PASSWORD_PMT") + self.base_url = os.environ.get("URL_PMT") + + def get_week_range(self, week_offset): + """Get Monday date for week_offset weeks from now""" + today = datetime.now() + days_to_monday = 0 if today.weekday() == 0 else 7 - today.weekday() + monday = today + timedelta(days=days_to_monday) + target_monday = monday + timedelta(weeks=week_offset) + return target_monday.strftime("%Y-%m-%d") + + def login(self): + self.session = requests.Session() + + login_url = f"{self.base_url}/login" + resp = self.session.get(login_url) + + # Step 2: SSO login POST + sso_data = { + "username": self._username, + "password": self._password, + "browserinfo": { + "osname": "Linux 148", + "browsername": "Firefox", + "browserversion": "148", + "screenresolution": "1080x1920" + } + } + + sso_resp = self.session.post(f"{self.base_url}/pmtLoginSso", + json=sso_data, + headers={'Content-Type': 'application/json'}) + + if sso_resp.status_code != 200 or not sso_resp.json().get('result', {}).get('authenticated'): + print("Login failed!") + print(sso_resp.text) + sys.exit(1) + + login_data = sso_resp.json()['result'] + self.context_token = login_data['context_token'] + self.user_token = login_data['user_token'] + + print(f"āœ… Logged in as {self._username} (store AH 8541 Rhoon)") + return self.session, self.context_token, self.user_token + + def get_shifts(self, days_ahead=14): + """Fetch shifts from now to days_ahead using the REAL endpoint""" + if not self.session or not self.context_token or not self.user_token: + raise ValueError("Must call login() first!") + + shifts = [] + + from_date = datetime.now().strftime("%Y-%m-%d") + to_date = (datetime.now() + timedelta(days=days_ahead)).strftime("%Y-%m-%d") + + print(f"Fetching shifts from {from_date} to {to_date}...") + + headers = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'en-US,en;q=0.9', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + 'Referer': 'https://ah8541.personeelstool.nl/my-overview/my-schedule/11-2026', + 'Content-Type': 'application/json', + 'x-api-user': self.user_token, + 'x-api-context': self.context_token, + 'Connection': 'keep-alive', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin' + } + + shifts_url = f"{self.base_url}/api/v3/environments/21/stores/311/employee/98104/simpleShifts" + params = { + 'from_date': from_date, + 'to_date': to_date + } + + resp = self.session.get(shifts_url, headers=headers, params=params) + + if resp.status_code == 200: + data = resp.json() + week_shifts = data.get('result', {}).get('shift_instances', []) + shifts.extend(week_shifts) + print(f"āœ… Got {len(week_shifts)} shifts!") + else: + print(f"āŒ Failed: {resp.status_code}") + print(resp.text[:500]) + + return shifts + + @classmethod + def print_shifts(cls, shifts): + """Pretty print shifts (class method since it doesn't need instance)""" + if not shifts: + print("No upcoming shifts found.") + return + + print("\nšŸ“… UPCOMING SHIFTS:\n") + print("Date | Time | Dept | Duration | Description") + print("-" * 80) + + for shift in sorted(shifts, key=lambda x: x.get('start_datetime', '')): + start = shift.get('start_datetime', 'N/A') + end = shift.get('end_datetime', 'N/A') + dept = shift.get('department_name', 'N/A') + duration = shift.get('duration', 'N/A') + desc = shift.get('shift_remark', shift.get('description', 'N/A')) + + date_str = start[:10] if start != 'N/A' else 'N/A' + time_str = f"{start[11:16]}-{end[11:16]}" if start != 'N/A' and end != 'N/A' else 'N/A' + + print(f"{date_str} | {time_str:<12} | {dept:<10} | {duration:<8} | {desc[:40]}") + diff --git a/__pycache__/DB.cpython-313.pyc b/__pycache__/DB.cpython-313.pyc new file mode 100644 index 0000000..6d84096 Binary files /dev/null and b/__pycache__/DB.cpython-313.pyc differ diff --git a/__pycache__/PMT.cpython-313.pyc b/__pycache__/PMT.cpython-313.pyc new file mode 100644 index 0000000..ce3c65e Binary files /dev/null and b/__pycache__/PMT.cpython-313.pyc differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..1f7c151 --- /dev/null +++ b/main.py @@ -0,0 +1,24 @@ +from PMT import PMT +from DB import Database +from dotenv import load_dotenv +import os + +def main(): + load_dotenv(override=True) + + db = Database() + pmt = PMT() + + + pmt.login() + + days_ahead = int(os.environ.get("DAYS_TO_FETCH")) + shifts = pmt.get_shifts(days_ahead) + PMT.print_shifts(shifts) + + db.insert_shifts(shifts) + + print(f"\nFound {len(shifts)} shifts total.") + +if __name__ == "__main__": + main()