Wieder einen halben Tag verschwendet nur um ein grauenhaft schlechtes Online Tool zu bedienen.
“I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.”
– Bill Gates
Die Idee
Ein kleines Tool schreiben, das mir jeden Tag auf das Knopferl “Hallo ich bin jetzt da und arbeite” drückt.
Wenn das Pensum erfüllt ist, soll das Programm auf “Auf wiederschaun, i bin weg” drücken.
Zum Glück gibts das auf der Web-Oberfläche. Verwendet nur niemand.
Aber an welchen Tagen soll es klicken?
Wie lang soll es warten bis es sich wieder ausloggt?
Und wann soll es Homeoffice schreiben und wann ist Office Tag?
Was ist mit Urlaub oder Feiertagen?
Das entscheidet dieses Flowchart:
Zum Glück gibts auf der Web-Oberfläche auch eine Sollzeit für den heutigen Tag! Die kann man auslesen und sich danach richten.
Steht da 0 Stunden? Passt, ausloggen und zurücklehnen.
Steht da was anderes? Log dich ein. Schau dir die aktuelle Uhrzeit an, addiere die Sollzeit dazu und schreib die Zeit auf. Komm um diese Uhrzeit wieder um dich auszuloggen. Easy.
Python Code
Gibts auf Github: https://github.com/thazaubara/i-work-hard
Im Grunde läuft das Programm alle 10 Minuten auf meinem Server durch. Je nach Status entscheidet es sich für einloggen, ausloggen oder einfach warten. Man muss ja nicht immer die Web-Oberfläche abfragen, spart Ressourcen. Dafür muss man aber mitschreiben um den aktuellen Status zu kennen. Ah ja und Montag und Donnerstag ist Office Tag, da klick ma was anderes!
Ubuntu Server
24/7 Online. Python file läuft alle 10 Minuten als cronjob durch.
24/7 Online. Python file läuft alle 10 Minuten als cronjob durch.
Steht dann auch hübsch im syslog drinnen:
zaubara@ubuntu-server:~/scripts/i-work-hard$ tail -f /var/log/syslog | grep 'I WORK HARD'
Dec 4 09:00:22 ubuntu-server zaubara: I WORK HARD at 04.12.2023 09:00 -> Normal booking booked.
Dec 4 09:00:22 ubuntu-server zaubara: I WORK HARD at 04.12.2023 09:00 -> Logged out. Quitting Driver.
Dec 4 17:30:21 ubuntu-server zaubara: I WORK HARD at 04.12.2023 17:30 -> Go home.
Dec 4 17:30:21 ubuntu-server zaubara: I WORK HARD at 04.12.2023 17:30 -> Logged out. Quitting Driver.
Dec 5 09:00:26 ubuntu-server zaubara: I WORK HARD at 05.12.2023 09:00 -> Homeoffice booked.
Dec 5 09:00:26 ubuntu-server zaubara: I WORK HARD at 05.12.2023 09:00 -> Logged out. Quitting Driver.
Dec 5 17:30:20 ubuntu-server zaubara: I WORK HARD at 05.12.2023 17:30 -> Go home.
Dec 5 17:30:20 ubuntu-server zaubara: I WORK HARD at 05.12.2023 17:30 -> Logged out. Quitting Driver.
Dec 6 09:00:22 ubuntu-server zaubara: I WORK HARD at 06.12.2023 09:00 -> Homeoffice booked.
Dec 6 09:00:22 ubuntu-server zaubara: I WORK HARD at 06.12.2023 09:00 -> Logged out. Quitting Driver.
Dec 6 17:30:21 ubuntu-server zaubara: I WORK HARD at 06.12.2023 17:30 -> Go home.
Dec 6 17:30:21 ubuntu-server zaubara: I WORK HARD at 06.12.2023 17:30 -> Logged out. Quitting Driver.
Dec 7 09:00:23 ubuntu-server zaubara: I WORK HARD at 07.12.2023 09:00 -> Normal booking booked.
Dec 7 09:00:23 ubuntu-server zaubara: I WORK HARD at 07.12.2023 09:00 -> Logged out. Quitting Driver.
Dec 7 17:30:20 ubuntu-server zaubara: I WORK HARD at 07.12.2023 17:30 -> Go home.
Dec 7 17:30:20 ubuntu-server zaubara: I WORK HARD at 07.12.2023 17:30 -> Logged out. Quitting Driver.
Selenium
Das beste Web-Testing Framework. Bis jetzt.
Hier das Getting Started: https://selenium-python.readthedocs.io/installation.html
Einfach erklärt:
“Such dir das Element mit dem xpath so-und-so und klick drauf”
“Such dir das Element mit der HTML id password und schreib die-eine-variable rein”
“Such dir das eine div und nimm das dritte element aus der Liste und kopiere den Wert”
Offizielle Doku hier: https://selenium-python.readthedocs.io/locating-elements.html
Also los gehts. Rechtsklick auf die Seite. Inspect Element.
Id raus kopieren und unter anderem folgende Zeilen schreiben:
# CLICK THE POST TOUCH BUTTON
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "TileButtonCID31513-btnWrap"))).click()
“Suche das Element TileButton-soundso, wenn nicht da, lass dir bis zu 10 Sekunden Zeit und versuchs nochmal. Wenn gefunden, klick drauf”. Sorry 4 Spaghetti Code, bei 15 von diesen Statements ists dann doch einfacher so zu lesen.
Lieber safe than sorry. Klickt man falsch, stürzt die Kiste ab und man hat einen von fünf möglichen Logins verkackt. Bei 5 von 5 muss man ca nen halben Tag warten bis wieder freigeschalten wird.
Mit solchen Statements geht man dann von Seite zu Seite, bis man bei den Elementen angekommen ist, die gewünscht sind.
- Seite aufrufen.
- Username einfügen
- Password einfügen
- Login Button klicken
- “Time” Button klicken
- “Post Touch” button klicken
- “Working Time Today” auslesen und aufschreiben
- Je nach Status:
- Klick “Posting Type” -> “Homeoffice”
- Klick “Posting Type” -> “Normalbuchung”
- Klick “Going”
- Ausloggen
- Fertig
Status File
Damit nicht jedes mal Selenium gestartet werden muss um nachzuschauen wie lang noch gearbeitet werden muss, schreiben wir einfach mit.
Wichtige Infos:
- Aktuelles Datum
- Aktueller Tag
wobei, nicht notwenig, ist aber lesbarer - Normalbuchung oder Homeoffice?
Tage an denen nicht gearbeitet wird, werden auch nicht mitgeschrieben - Startzeitpunkt
eh kloa - Endzeitpunkt
wann wird voraussichtlich wieder nachgeschaut wie lang ich noch hackeln muss? - Ist der Tag beendet?
eh kloa - Wann wurde wirklich ausgeloggt?
Wann wirklich ausgeloggt wurde.
Das kommt in ein Simples Textfile, das alle 10 Minuten vom Script ausgelesen wird. JSON Format. Das Langzeitgedächtnis von dem Programm.
[
...
{
"date": "06.12.2023",
"day": "Wednesday",
"action": "homeoffice",
"start": "09:00",
"end": "17:30",
"finished": "yes",
"logout_time": "17:30"
},
{
"date": "07.12.2023",
"day": "Thursday",
"action": "normalbuchung",
"start": "09:00",
"end": "17:30",
"finished": "yes",
"logout_time": "17:30"
}
]
Die eine Funktion
Herz des Ganzen ist eigentlich dieser Teil vom Code. Der Rest bezieht sich auf irgendwelche Date-checks oder File-mitschreib-Geschichten.
Info: sleep() ist eine Funktion von mir, nicht per se time.sleep(). Wartet einfach eine Sekunde und gibt mir mögliche Breakpoints zum Debuggen. Weil BMD ewig langsam ist.
def do_bmd_stuff(action, headless=True):
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
options.add_argument("--window-size=1300,800")
if headless:
options.add_argument("--start-maximized")
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
width = driver.get_window_size().get("width")
height = driver.get_window_size().get("height")
print(f"driver set to {width}x{height}")
# GO TO SITE
base_url = "https://*****/bmdweb2"
print(f"Loading {BMD_URL}")
driver.get(BMD_URL)
try:
# FILL CREDENTIALS
#txt_username = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "txtuser - inputEl")))
txt_username = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "txtuser-inputEl")))
txt_username.send_keys(BMD_USER)
txt_password = driver.find_element(By.ID, "txtpass-inputEl")
txt_password.send_keys(BMD_PASS)
# CLICK THE LOGIN BUTTON
loginbutton = driver.find_element(By.ID, "loginbutton-btnEl")
loginbutton.click()
# CLICK THE TIME BUTTON#
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "TileButtonPKG564-btnWrap"))).click()
print("Login Successful.")
# CLICK THE POST TOUCH BUTTON
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "TileButtonCID31513-btnWrap"))).click()
# GET WORKING TIME TODAY
day_debit = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "AttrFieldTextFieldContainer7612734691278121CID4089024UID184-inputEl"))).get_attribute("value")
day_so_far = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "AttrFieldTextFieldContainer7612734691278121CID4089025UID185-inputEl"))).get_attribute("value")
day_debit_float = 0.0
day_so_far_float = 0.0
try:
hours, minutes = map(int, day_debit.split(':'))
day_debit_float = hours + minutes / 60.0
hours, minutes = map(int, day_so_far.split(':'))
day_so_far_float = hours + minutes / 60.0
except:
print("Error parsing time. Exiting.")
sys.exit(1)
# print(f"Day debit: {day_debit_float}")
if action == action_homeoffice:
# CLICK POSTING TYPE
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn162-btnEl"))).click()
# CLICK "HOMEOFFICE"
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn287-btnEl"))).click()
# CLICK SAVE BUTTON
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn179-btnEl"))).click()
print_log("Homeoffice booked.")
elif action == action_normalbuchung:
# CLICK POSTING TYPE
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn162-btnEl"))).click()
# CLICK "NORMAL BOOKING"
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn281-btnEl"))).click()
# CLICK SAVE BUTTON
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn179-btnEl"))).click()
print_log("Normal booking booked.")
elif action == action_logout:
# CLICK GOING
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn159-btnEl"))).click()
# CLICK SAVE BUTTON
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "ButtonFrameBtn179-btnEl"))).click()
print_log("Go home.")
elif action == action_check_time:
print(f"Just checked Time.")
pass
else:
print("Unknown action. Returning.")
# TODO Logout. bc -> The max. number of 5 zulässigen Datenbankverbindungen pro Benutzer wurde überschritten!
# CLICK USER
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "NavBtnCMDUser77-btnInnerEl"))).click()
# CLICK LOGOOUT
sleep()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "NavBar68ItemCMDLogout-itemEl"))).click()
print_log("Logged out. Quitting Driver.")
driver.quit()
return day_debit_float, day_so_far_float
except Exception as e:
print_log(f"Error in Script. Exiting.")
print(e)
sys.exit(1)
Weitere Ideen
- Zufallsgenerator für etwas ungenauere Zeitbuchungen. Damits nicht so auffällig ist wenn sich das mal jemand anschaut.
- Telegram Bot, der mit Logs ausspuckt
- Telegram Bot, der das Script bei Bedarf steuert
- Vollautomatische Leistungserfassung zusätzlich. Für 0 Klicks im Monat.
Danach ist das Problem eh in Luft aufgelöst. Viel Spaß beim nachbauen 😛
Falls das jemand liest, der nicht so erfreut darüber ist:
– All-In Vertrag, wo es wichtig ist dass Aufgaben Erledigt werden und nicht, dass man so-und-so viele Stunden an der Arbeit sitzt.
– Alle tragen am Ende des Monats händisch die Stunden nach. Wie genau und nachvollziehbar das ist, kannst dir eh denken!
– Wenn ihr nen Affen an der Tastatur wollt, sucht euch nen anderen. Ich vergeude meine Zeit nicht mit Belanglosigkeiten wie Zeiterfassung.
– An die vom Arbeitsamt: jo eh.
Peace out!