├── .gitignore ├── logo.png ├── requirements.txt ├── correction.json ├── ReadMe.md ├── License.md └── watcher.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pkl 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mm4rx/42-correction-watcher/HEAD/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pync; sys_platform != 'win32' 2 | win10toast; sys_platform == 'win32' 3 | notify2; sys_platform == 'linux' 4 | requests 5 | dbus-python; sys_platform == 'linux' 6 | -------------------------------------------------------------------------------- /correction.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ids": "mEYzJtc6isk8dVvx_4xg0Q==,NZhwHq0fR3BLJ7Osmr6UXg==", 4 | "start": "2021-04-20T18:30:00.000+02:00", 5 | "end": "2021-04-20T19:00:00.000+02:00", 6 | "id": "mEYzJtc6isk8dVvx_4xg0Q==", 7 | "title": "Available" 8 | } 9 | ] -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # 42 correction watcher 2 | 3 | A Python script that will send you a desktop notification when a correction slot is found. 4 | 5 | ## Usage 6 | 7 | Only tested on macOS. 8 | ```sh 9 | # Use python 3 10 | pip3 install -r requirements.txt 11 | python3 watcher.py 12 | ``` 13 | 14 | ## Where to find the needed data? 15 | - project URL: in the URL of the correction board (projects.intra.42.fr/projects/**42cursus-ft_linear_regression**/slots?team_id=3547579 => 42cursus-ft_linear_regression). 16 | - team ID: in the URL of the correction board (projects.intra.42.fr/projects/42cursus-ft_linear_regression/slots?team_id=**3547579** => 3547579). 17 | - session token: in your cookies on the intra, under the name **_intra_42_session_production**. 18 | - api callback (optional): specify an url to call if a correction is found, use `{}` as a placeholder for the message. Example => "https://smsapi.free-mobile.fr/sendmsg?user=[censored]&pass=[censored]&msg={}" -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tom Marx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /watcher.py: -------------------------------------------------------------------------------- 1 | """Watch for a 42 correction slot. Author: tmarx.""" 2 | 3 | if (__name__ != "__main__"): 4 | exit(1) 5 | from sys import platform 6 | if platform == 'win32': 7 | from win10toast import ToastNotifier 8 | elif platform == 'darwin': 9 | import pync 10 | elif platform == "linux": 11 | import notify2 12 | import time 13 | import requests 14 | import datetime 15 | import pickle 16 | 17 | # Time waited between every check, in seconds 18 | WAIT_TIME = 15 19 | 20 | def get_params(): 21 | """Get the last params saved on the disk. Return None if no params saved.""" 22 | try: 23 | with open("./params.pkl", "rb") as file: 24 | url, team_id, token, api = pickle.load(file) 25 | return (url, team_id, token, api) 26 | except: 27 | return (None, None, None, None) 28 | 29 | def save_params(url, team_id, token, api): 30 | """Save params on the disk.""" 31 | with open("./params.pkl", "wb+") as file: 32 | pickle.dump((url, team_id, token, api), file) 33 | 34 | def notify(title, message): 35 | """Send a desktop notification.""" 36 | if platform == 'darwin': 37 | pync.notify(message, title=title, contentImage="./logo.png", sound="default") 38 | elif platform == 'win32': 39 | toast = ToastNotifier() 40 | toast.show_toast(title, message, duration=0) 41 | elif platform == "linux": 42 | notify2.init("42 School Correction") 43 | n = notify2.Notification(title, message) 44 | n.show() 45 | 46 | def get_slots(): 47 | """Send a request to 42 intra.""" 48 | today = datetime.date.today() 49 | start = today 50 | end = today + datetime.timedelta(days=days_to_watch) 51 | print(f"Getting slots from {today} to {end}") 52 | request = requests.get( 53 | f"https://projects.intra.42.fr/projects/{project_url}/slots.json?team_id={team_id}&start={today}&end={end}", 54 | headers={ 55 | "Host": "projects.intra.42.fr", 56 | "Cookie": f"_intra_42_session_production={token};" 57 | } 58 | ) 59 | response = request.json() 60 | print(response) 61 | if ("error" in response): 62 | print("Unable to connect, check the token!") 63 | exit(0) 64 | return response 65 | 66 | # Do the callback if specified 67 | def call_api(message): 68 | try: 69 | requests.get(api.format(message)) 70 | except Exception as e: 71 | print(e) 72 | 73 | # Check if we previously saved params 74 | previous_url, previous_team_id, previous_token, previous_api = get_params() 75 | 76 | url_placeholder = "" 77 | team_id_placeholder = "" 78 | token_placeholder = "" 79 | api_placeholder = "" 80 | if (not previous_url is None): 81 | url_placeholder = f" (default {previous_url})" 82 | if (not previous_team_id is None): 83 | team_id_placeholder = f" (default {previous_team_id})" 84 | if (not previous_token is None): 85 | token_placeholder = f" (default {previous_token})" 86 | if (not previous_api is None): 87 | api_placeholder = f" (default {previous_api})" 88 | 89 | # Get variables 90 | print("Refer to ReadMe.md to help you find the following data.") 91 | 92 | project_url = input(f"Project URL{url_placeholder}: ") 93 | if (project_url == ""): 94 | project_url = previous_url 95 | 96 | team_id = input(f"Team ID{team_id_placeholder}: ") 97 | if (team_id == ""): 98 | team_id = previous_team_id 99 | 100 | token = input(f"Session token{token_placeholder}: ") 101 | if (token == ""): 102 | token = previous_token 103 | 104 | api = input(f"API callback (optional){api_placeholder}: ") 105 | if (api == ""): 106 | api = previous_api 107 | 108 | try: 109 | days_to_watch = int(input("Number of days you want to watch (default 3): ")) 110 | except: 111 | days_to_watch = 3 112 | 113 | save_params(project_url, team_id, token, api) 114 | 115 | notify("Running", "You'll be notified when an open slot is found.") 116 | 117 | while True: 118 | print("Checking...") 119 | slots = get_slots() 120 | if (len(slots) > 0): 121 | print("Slot found.") 122 | notify("Correction found!", "An open slot has been found.") 123 | if api: 124 | call_api("42: Correction Found") 125 | time.sleep(WAIT_TIME) 126 | --------------------------------------------------------------------------------