├── assets
├── step_1.png
└── step_2.png
├── .gitignore
├── requirements.txt
├── Makefile
├── README.md
├── LICENSE.md
├── cli.py
└── slotter.py
/assets/step_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awbx/slotter/HEAD/assets/step_1.png
--------------------------------------------------------------------------------
/assets/step_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awbx/slotter/HEAD/assets/step_2.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore .env file
2 | .env
3 | # ignore env folder
4 | env
5 | # ignore __pycache__ folder
6 | __pycache__
7 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | beautifulsoup4==4.11.1
2 | python-dotenv==0.21.0
3 | requests==2.28.1
4 | click==8.1.3
5 | bs4==0.0.1
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | install:
5 | touch .env && echo "INTRA_SESSION_ID=YOUR INTRA SESSION ID" > .env # TODO check if .env file exists to avoid overwriting.
6 | python3.9 -m pip install -r requirements.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Slotter
2 | This's a slotter CLI, that will fill/remove the available slots.
3 |
4 | PS: this is a beta version
5 |
6 | ## installation
7 |
8 | ```bash
9 | # using Makefile
10 | make install
11 |
12 | # manually install
13 | python3.9 -m pip install -r requirements.txt
14 | ```
15 |
16 | ## usage
17 | -First of all, you should create a .env file that will contain the following content:
18 | ```
19 | INTRA_SESSION_ID=YOUR INTRA SESSION ID
20 | ```
21 | -Follow the steps in the screenshots to get your session id.
22 | * Step1 -> go to Cookies
23 |
24 |
25 | * Step2 -> Copy the `_intra_42_session_production` content
26 |
27 |
28 |
29 | * take slots
30 | - `python3.9 cli.py take-slots`
31 | * delete slots
32 | - `python3.9 cli.py delete-slots`
33 |
34 | ## TODO
35 |
36 | - Add User-Agent to act like a browser
37 | - Add more options(features) to `take-slots` command
38 | - Add day range feature to `take-slots` command
39 |
40 | ## License
41 | [MIT](./LICENSE.md)
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Abdelhadi Sabani
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 |
--------------------------------------------------------------------------------
/cli.py:
--------------------------------------------------------------------------------
1 | from dotenv import load_dotenv
2 | from slotter import Slotter
3 | import click, sys, os
4 |
5 | # load the .env variables
6 | load_dotenv()
7 |
8 | session_id = os.environ.get("INTRA_SESSION_ID")
9 |
10 | if not session_id:
11 | print("Please add your INTRA session id!", file=sys.stderr)
12 | sys.exit(1)
13 |
14 | # create an instance from Slotter class
15 |
16 | slotter = Slotter(session_id)
17 |
18 | # login with session intra and check if valid
19 |
20 | if not slotter.login():
21 | print("Please add a valid INTRA session id!", file=sys.stderr)
22 | sys.exit(1)
23 |
24 |
25 | @click.group()
26 | def cli():
27 | "Slotter Command line interface."
28 |
29 | def validate_duration(ctx, param, value):
30 | if value < 30:
31 | raise click.BadParameter('duration should be greate than 30.')
32 | return value
33 |
34 |
35 | @cli.command
36 | @click.option("--duration", "-d", type=int, callback=validate_duration, default=30)
37 | def take_slots(duration):
38 | """take the untaken slots"""
39 | slotter.take_slots(duration)
40 |
41 |
42 |
43 | @cli.command
44 | def delete_slots():
45 | """delete the taken slots"""
46 | slotter.delete_slots()
47 |
48 | if __name__ == "__main__":
49 | cli(prog_name='slotter')
--------------------------------------------------------------------------------
/slotter.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 | from requests import session
3 | import datetime
4 |
5 | BASE_URL = "https://profile.intra.42.fr"
6 |
7 |
8 | class Slotter:
9 | def __init__(self, session_id):
10 | self.sess = session()
11 | self.session_id = session_id
12 |
13 | def login(self):
14 | self.sess.cookies.update(
15 | {"_intra_42_session_production": self.session_id})
16 | resp = self.sess.get(
17 | f"{BASE_URL}/", allow_redirects=False)
18 | if resp.is_redirect:
19 | return False
20 | soup = BeautifulSoup(resp.text, "html.parser")
21 | csrf_meta = soup.find("meta", attrs={"name": "csrf-token"})
22 | self.sess.headers['X-Csrf-Token'] = csrf_meta.get("content")
23 | return True
24 |
25 | def take_slots(self, duration=30):
26 | start = datetime.datetime.now() + datetime.timedelta(minutes=60)
27 | while True:
28 | data = {
29 | "slot[begin_at]": start.strftime("%Y-%m-%dT%H:%M:%S"),
30 | "slot[end_at]": (start + datetime.timedelta(minutes=duration)).strftime("%Y-%m-%dT%H:%M:%S"),
31 | }
32 | resp = self.sess.post(
33 | f"{BASE_URL}/slots.json", data=data)
34 | payload = resp.json()
35 | if payload['message'].startswith('Has') or payload['message'].startswith('No overlapping!'):
36 | start += datetime.timedelta(minutes=30)
37 | continue
38 | elif payload['message'].startswith('Ending') and payload['status'] != 200:
39 | break
40 | start = datetime.datetime.strptime(payload.get(
41 | 'data')['end'][:-6], '%Y-%m-%dT%H:%M:%S.%f')
42 | start += datetime.timedelta(minutes=30)
43 | print("Taking slots...", end='\r')
44 |
45 | def delete_slots(self):
46 | start = datetime.datetime.now()
47 | end = start + datetime.timedelta(minutes=60 * 24 * 30)
48 | resp = self.sess.get(f'{BASE_URL}/slots.json', params={
49 | "start": start.strftime("%Y-%m-%d"),
50 | "end": end.strftime("%Y-%m-%d")})
51 | slots = resp.json()
52 | if not slots:
53 | print("There's no slots to delete!")
54 | return
55 | for slot in slots:
56 | resp = self.sess.post(f"{BASE_URL}/slots/{slot['id']}.json", data={
57 | "ids": slot['ids'],
58 | "_method": "delete",
59 | "confirm": False
60 | })
61 | print("Deleting Slots...", end="\r")
62 |
--------------------------------------------------------------------------------