-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'release/v.0.01.0-beta'
- Loading branch information
Showing
4 changed files
with
295 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
.idea | ||
__pycache__ | ||
test | ||
test.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,101 @@ | ||
# IServ.py <img src="https://iserv.de/downloads/logo/IServ_Logo_klein_RGB_clean.svg" alt="" height="25" align="right"> | ||
# IServ.py <img src="https://iserv.de/downloads/logo/IServ_Logo_klein_RGB_clean.svg" alt="" height="35" align="right"> | ||
|
||
`Made by RoRo160` `v.0.01.0-beta` | ||
|
||
This module provides an easy way to communicate with your IServ account. | ||
I reverse-engineered parts of the internal IServ api and recreated some http requests your browser would do in the | ||
background. | ||
|
||
> 🔴 **WARNING:** <br/> | ||
> This module does **NOT** use an official api! | ||
> | ||
> Extensive usage might lead to problems with your IServ account. | ||
> | ||
> **USE AT YOUR OWN RISK!!!** | ||
### Features: | ||
|
||
- Login/Logout with your IServ account | ||
- Plan changes, by week, day, filtered by course | ||
- Get all tasks | ||
|
||
### Coming soon: | ||
|
||
- Summarized representation plan only with entries from your class | ||
- Get all tasks | ||
- Read all your emails | ||
- Notifications | ||
|
||
> **WARNING:** <br/> | ||
> This module does **NOT** use an official api! <br/> | ||
> Extensive usage might lead to problems with your IServ account. <br/> | ||
> <br/> | ||
> **USE AT YOUR OWN RISK!!!** | ||
## Usage: | ||
|
||
> ⚠ **Note:** | ||
> To run you might need to install some dependencies. | ||
> | ||
> Do that by running the following command: | ||
> ````shell | ||
> pip install -r requirements.txt | ||
> ```` | ||
### Login/Logout: | ||
````python | ||
from iserv import * | ||
# create IServ object, link your IServ server | ||
# (please add "https://" before domain name and don't add a "/" at the end!) | ||
iserv = IServ("https://your.iserv.example") | ||
# login to your account | ||
iserv.login("your.username", "password") | ||
# >>> do what ever you want here <<< | ||
# do NOT forget to logout | ||
iserv.logout() | ||
```` | ||
> #### 💡 **Advise:** Time delay between requests | ||
> Many requests in a short time period may seem suspicious to the server. | ||
> | ||
> To prevent any issues with your account add a **random time delay** between requests. | ||
> | ||
> You can do this as shown here: | ||
> | ||
> ````python | ||
> import time | ||
> import random | ||
> | ||
> # first request | ||
> | ||
> # random break between two requests | ||
> time.sleep(random.uniform(0.5, 10.0)) | ||
> | ||
> # second request | ||
> ```` | ||
> | ||
### How to use all the features? | ||
#### Plan changes: | ||
````python | ||
changes = iserv.plan_changes( | ||
courses=["7c", "10b"], | ||
days=["monday", "thursday"] | ||
) | ||
# do something with plan changes dictionary | ||
```` | ||
#### Tasks: | ||
````python | ||
tasks = iserv.tasks( | ||
status="current", | ||
sort_by="enddate", | ||
sort_dir="DESC" | ||
) | ||
|
||
# do something with task list | ||
```` | ||
|
||
# | ||
|
||
> By RoRo160 `v.0.01.0-beta` | ||
> | ||
> [My GitHub](https://github.com/RoRo160) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import datetime | ||
import requests | ||
import bs4 | ||
from bs4 import * | ||
|
||
|
||
DAYS = [ | ||
"monday", | ||
"tuesday", | ||
"wednesday", | ||
"thursday", | ||
"friday" | ||
] | ||
|
||
|
||
class LoginError(Exception): | ||
pass | ||
|
||
|
||
class IServ: | ||
paths = { | ||
"login": "/iserv/app/login", | ||
"logout": "/iserv/app/logout", | ||
"plan": "/iserv/plan/show/raw/", | ||
"tasks": "/iserv/exercise.csv" | ||
} | ||
messages = { | ||
"login_failed": "Anmeldung fehlgeschlagen!" | ||
} | ||
|
||
def __init__(self, domain): | ||
self.domain = domain | ||
self._csrf_token = None | ||
self._s = requests.Session() | ||
|
||
def login(self, user: str, pw: str): | ||
# send post request with session object | ||
r = self._s.post( | ||
url=self.domain + IServ.paths['login'], # login path | ||
data=f"_password={pw}&_username={user}", # pw ad user in body of request | ||
headers={ | ||
"Content-Type": "application/x-www-form-urlencoded" # tell server kind of form, necessary | ||
} | ||
) | ||
|
||
# check if login was successful | ||
# TODO check in header | ||
if IServ.messages["login_failed"] in r.text: | ||
raise LoginError("Login failed") | ||
|
||
# find and store csrf token, needed on logout | ||
self._csrf_token = self._find_csrf(r.text) | ||
|
||
# return True if login was successful | ||
return True | ||
|
||
def logout(self): | ||
self._s.get( | ||
# login path | ||
url=self.domain + IServ.paths['logout'], | ||
# add csrf token to query | ||
params={"_csrf": self._csrf_token} | ||
) | ||
|
||
@staticmethod | ||
def _find_csrf(doc: str): | ||
s = BeautifulSoup(doc, "html.parser") | ||
tag = s.find('body')\ | ||
.find_all('div')[0]\ | ||
.find_all('div')[0]\ | ||
.find_all('ul')[0]\ | ||
.find_all('li')[1]\ | ||
.find('div')\ | ||
.find('ul')\ | ||
.find_all('li')[4]\ | ||
.find('a') | ||
|
||
return tag.attrs["href"].split('=')[-1] | ||
|
||
def plan_changes( | ||
self, | ||
courses=None, | ||
days=None, | ||
week: int = datetime.date.today().isocalendar()[1], | ||
plan_name="0_Vertretungen (Schüler)" | ||
): | ||
# default args | ||
if days is None: | ||
days = [""] | ||
if type(days) == str: | ||
days = [days] | ||
days = [d.lower() for d in days] | ||
|
||
if courses is None: | ||
courses = [""] | ||
if type(courses) == str: | ||
courses = [courses] | ||
courses = [c.lower() for c in courses] | ||
|
||
# TODO on sunday next week | ||
# get doc | ||
r = self._s.get( | ||
url=self.domain + self.paths["plan"] + plan_name + "/" + str(week) + "/w/w00000.htm" | ||
) | ||
# validate return | ||
if not r.headers["content-disposition"] == "inline; filename=w00000.htm": | ||
raise Exception("invalid return") | ||
|
||
# create soup | ||
s = BeautifulSoup(r.text, "html.parser") # , from_encoding="utf-8") | ||
|
||
# get list of needed tables in doc | ||
tables = s.find_all("table", class_="subst") | ||
|
||
entries = {} | ||
|
||
for i in range(len(tables)): | ||
# get days | ||
day_temp = DAYS[i] | ||
# check day | ||
if days == [""]: | ||
pass | ||
elif day_temp not in days: | ||
# skip this table | ||
continue | ||
|
||
entries[day_temp] = [] | ||
|
||
# iterate through all rows of table and store data | ||
for row in tables[i]: | ||
if type(row) != bs4.element.NavigableString: | ||
# get all values of columns | ||
clm = [i.string for i in row.find_all("td")] | ||
try: | ||
add = False | ||
for course in courses: | ||
add_temp = True | ||
for letter in course: | ||
if letter not in clm[0].lower(): | ||
add_temp = False | ||
if add_temp: | ||
add = True | ||
if add: | ||
details = { | ||
"courses": clm[0], | ||
"hour": clm[1], | ||
"subject": clm[2], | ||
"teacher": clm[3], | ||
"room": clm[4], | ||
"comments": clm[5], | ||
"org_subject": clm[6], | ||
"org_teacher": clm[7], | ||
"type": clm[8], | ||
} | ||
# print(details) | ||
entries[day_temp].append(details) | ||
except IndexError: | ||
pass | ||
return entries | ||
|
||
def tasks( | ||
self, | ||
status: str = "current", | ||
sort_by: str = "enddate", | ||
sort_dir: str = "DESC" | ||
): | ||
# TODO add option to filter tasks | ||
|
||
r = self._s.get( | ||
url=self.domain + IServ.paths["tasks"], | ||
params={ | ||
"filter[status]": status, | ||
"sort[by]": sort_by, | ||
"sort[dir]": sort_dir | ||
} | ||
) | ||
|
||
tasks = [] | ||
|
||
tasks_ = r.text.splitlines() | ||
tasks_.pop(0) | ||
|
||
# TODO use csv.reader to solve problems with quoted values and separators between those | ||
# tasks_ = csv.reader(r.text, quotechar='"') | ||
|
||
for t in tasks_: | ||
task = t.split(";") | ||
for i in range(len(task)): | ||
if task[i].lower() == "ja": | ||
task[i] = True | ||
elif task[i].lower() == "nein": | ||
task[i] = False | ||
|
||
tasks.append({ | ||
"task": task[0], | ||
"startdate": task[1], | ||
"enddate": task[2], | ||
"tags": task[3], | ||
"done": task[4], | ||
"review": task[5], | ||
}) | ||
|
||
return tasks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
requests~=2.25.1 | ||
beautifulsoup4~=4.9.3 |