From f48aa34bc05b705f99ac03d121a25b66e83ec8aa Mon Sep 17 00:00:00 2001 From: tomoron Date: Fri, 28 Mar 2025 13:16:59 +0100 Subject: [PATCH] initial commit --- __pycache__/login.cpython-312.pyc | Bin 0 -> 10305 bytes login.py | 139 ++++++++++++++++++++++++++++++ scrap.py | 40 +++++++++ 3 files changed, 179 insertions(+) create mode 100644 __pycache__/login.cpython-312.pyc create mode 100755 login.py create mode 100644 scrap.py diff --git a/__pycache__/login.cpython-312.pyc b/__pycache__/login.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d96f9c793be6c1fb49747e76b98f9a1156b2ee2e GIT binary patch literal 10305 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 +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2024/11/25 16:22:08 by tomoron #+# #+# # +# Updated: 2025/03/26 13:10:42 by tomoron ### ########.fr # +# # +# **************************************************************************** # + +import requests +import time +import subprocess +import os +import re +import json +import urllib.parse +from getpass import getpass +from bs4 import BeautifulSoup + + +class Intra42(): + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0 .6167.160 Safari/537.36" + + def __init__(self): + self.cookieHeader = self.get_cookie_header() + print("token ready") + + def update_cookies(self, cookies, new_cookies, path): + cookies_arr = re.compile("^[A-Za-z_.0-9]+=.*?;", re.MULTILINE).findall("\n".join(new_cookies.split(', '))) + for x in cookies_arr: + cookies[x.split("=")[0]] = x.split("=")[1][:-1] + with open(os.path.expanduser(path), 'w') as f: + json.dump(cookies, f) + return(cookies) + + def cookie_to_header(self, cookies): + cookie_header = "" + for x in cookies: + cookie_header += x + "="+cookies[x] +"; " + cookie_header = cookie_header[:-2] + return(cookie_header) + + def login_user(self, username, password): + response = requests.get("https://profile.intra.42.fr/users/auth/keycloak_student", headers={"User-Agent": Intra42.user_agent}, allow_redirects=False) + login_url = response.headers["Location"] + response = requests.get(login_url, headers={"User-Agent":Intra42.user_agent}, allow_redirects=False); + cookies_arr = [x.split('; ')[0] for x in response.headers["Set-Cookie"].split(', ')] + cookies = {} + for x in cookies_arr: + cookies[x.split("=")[0]] = x.split("=")[1] + + page = BeautifulSoup(response.text, "html.parser") + form_html = page.find_all("form",id="kc-form-login")[0] + form_start = re.compile("^").findall(str(form_html))[0] + url = form_start.split(' ')[1].split("\"")[1] + + username = urllib.parse.quote(username) + password = urllib.parse.quote(password) + cookie_header = self.cookie_to_header(cookies) + req_body = f'username={username}&password={password}&rememberMe=on&credentialId=' + url = url.replace("amp;", "") + headers = { + "User-Agent":Intra42.user_agent, + "Cookie":cookie_header, + "Content-Type": "application/x-www-form-urlencoded", + } + response = requests.post(url, data=req_body ,headers=headers,allow_redirects=False) + if(response.status_code == 200): + return("error") + return(self.update_cookies(cookies, response.headers["Set-Cookie"],"~/.intra_refresh")) + + def refresh_session(self): + cookies = "error" + if(os.path.isfile(os.path.expanduser("~/.intra_refresh"))): + with open(os.path.expanduser("~/.intra_refresh"), 'r') as f: + cookies = json.load(f) + else: + while(cookies == "error"): + username = input("enter intra username :") + password = getpass("enter password :") + cookies = self.login_user(username, password) + response = requests.get("https://profile.intra.42.fr/users/auth/keycloak_student",headers={"User-Agent":Intra42.user_agent},allow_redirects=False) + intra_prod_cookie = response.cookies.get("_intra_42_session_production") + cookie_header = self.cookie_to_header(cookies) + response = requests.get(response.headers["Location"], headers={"User-Agent":Intra42.user_agent,"Cookie":cookie_header},allow_redirects=False) + if(response.status_code == 200): + os.remove(os.path.expanduser("~/.intra_refresh")) + return(self.refresh_session()) + self.update_cookies(cookies, response.headers["Set-Cookie"], "~/.intra_refresh") + response = requests.get(response.headers["Location"], headers={"User-Agent":Intra42.user_agent,"Cookie":f"_intra_42_session_production={intra_prod_cookie}"},allow_redirects=False) + profile_cookies = self.update_cookies({},response.headers["Set-Cookie"],"~/.intra_profile") + return(profile_cookies); + + def get_cookie_header(self): + profile_cookies = {} + if(os.path.isfile(os.path.expanduser("~/.intra_profile"))): + with open(os.path.expanduser("~/.intra_profile")) as f: + profile_cookies = json.load(f) + else: + profile_cookies = self.refresh_session() + return(self.cookie_to_header(profile_cookies)) + + def get_intra_home(self): + self.cookieHeader = self.get_cookie_header() + cookie_header = self.cookieHeader + cookie_header += "; intra=v2" + cookie_header += "; locale=en" + response = requests.get("https://profile.intra.42.fr/",headers = {"User-Agent":Intra42.user_agent,"Cookie":cookie_header},allow_redirects=False) + if(response.status_code == 302): + os.remove(os.path.expanduser("~/.intra_profile")) + return(get_intra_home(self.get_cookie_header())) + return(response.text) + + def get_goals(self): + self.cookieHeader = self.get_cookie_header() + response = requests.get("https://profile.intra.42.fr/users/me/goals?cursus=42cursus",headers = {"User-Agent":Intra42.user_agent,"Cookie":self.cookieHeader},allow_redirects=False) + if(response.status_code == 302): + os.remove(os.path.expanduser("~/.intra_profile")) + return(get_goals()) + return(response.text) + + def get_project_page(self, slug): + self.cookieHeader = self.get_cookie_header() + response = requests.get(f"https://projects.intra.42.fr/projects/{slug}",headers = {"User-Agent":Intra42.user_agent,"Cookie":self.cookieHeader},allow_redirects=False) + if(response.status_code == 302): + os.remove(os.path.expanduser("~/.intra_profile")) + return(get_project_page(slug)) + return(response.text) + + def get_available_slot(self, url): + self.cookieHeader = self.get_cookie_header() + response = requests.get(url, headers = { "User-Agent": Intra42.user_agent, "Cookie":self.cookieHeader}, allow_redirects=False) + if(response.status_code == 302): + os.remove(os.path.expanduser("~/.intra_profile")) + return(get_available_slot(url)) + return(json.loads(response.text)) diff --git a/scrap.py b/scrap.py new file mode 100644 index 0000000..a9d90e7 --- /dev/null +++ b/scrap.py @@ -0,0 +1,40 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# scrap.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: tomoron +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2024/11/25 16:39:19 by tomoron #+# #+# # +# Updated: 2025/03/28 13:13:48 by tomoron ### ########.fr # +# # +# **************************************************************************** # + +from login import Intra42 +from getpass import getpass +import re +import json +import threading +import time +import sys +import dateutil +from datetime import date, timedelta + +if(len(sys.argv) != 2): + print("missing team id, usage :", sys.argv[0], "") + exit(1); +project_id = sys.argv[1] +start_search_date = date.today().strftime("%Y-%m-%d") +end_search_date = (date.today() + timedelta(days=1)).strftime("%Y-%m-%d") +getUrl = f"https://projects.intra.42.fr/projects/{project_id}/slots.json?start={start_search_date}&end={end_search_date}" +connIntra = Intra42() +found = set(); +while(True): + res = connIntra.get_available_slot(getUrl) + for x in res: + if x["ids"] not in found: + start = dateutil.parser.isoparse(x["start"]).strftime("%d/%m %H:%M") + end = dateutil.parser.isoparse(x["end"]).strftime("%d/%m %H:%M") + print("\aslot found starting at", start, ",ending at", end); + found.add(x["ids"]) + time.sleep(1);