wip: add get_hours_present method

master
Maximilian Zettler 4 years ago
parent e32192f8dd
commit 2ab0d6d920
  1. 23
      timebot/app.py
  2. 37
      timebot/constants.py
  3. 9
      timebot/gui.py
  4. 62
      timebot/timebot.py

@ -5,7 +5,7 @@ import logging
import os import os
import sys import sys
from timebot.constants import SIMPLE_DATETIME_FORMAT_HUMAN, PUNCH_COMMANDS, SIMPLE_DATE_FORMAT, SIMPLE_TIME_FORMAT from timebot.constants import PUNCH_COMMANDS, DateFormats
from timebot.timebot import MobatimeApi, TimeBot, parse_user_time_input from timebot.timebot import MobatimeApi, TimeBot, parse_user_time_input
logger = logging.getLogger() logger = logging.getLogger()
@ -24,18 +24,18 @@ def run():
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
# subparser command: `status` # subparser command: `status`
parser_status = subparsers.add_parser("status", help="show your current tracking status") subparsers.add_parser("status", help="show your current tracking status")
# subparser command: `gui` # subparser command: `gui`
parser_status = subparsers.add_parser("gui", help="start qt5 gui") subparsers.add_parser("gui", help="start qt5 gui")
# subparser command: `list-entries` # subparser command: `list-entries`
parser_list_entries = subparsers.add_parser("list-entries", help="use this command to list your time entries") parser_list_entries = subparsers.add_parser("list-entries", help="use this command to list your time entries")
parser_list_entries.add_argument("--start-date", parser_list_entries.add_argument("--start-date",
help=f"start date filter in format `{SIMPLE_DATETIME_FORMAT_HUMAN}` " help=f"start date filter in format `{DateFormats.SIMPLE_DATETIME.evalue}` "
f"(default: now - 10days; unset for default)") f"(default: now - 10days; unset for default)")
parser_list_entries.add_argument("--end-date", parser_list_entries.add_argument("--end-date",
help=f"end date filter in format `{SIMPLE_DATETIME_FORMAT_HUMAN}` " help=f"end date filter in format `{DateFormats.SIMPLE_DATETIME.evalue}` "
f"(default: now; unset for default)") f"(default: now; unset for default)")
parser_list_entries.add_argument("--items", help="max items to request per page", default=20, type=int) parser_list_entries.add_argument("--items", help="max items to request per page", default=20, type=int)
@ -46,8 +46,9 @@ def run():
help=f"type of time entry; this can be {', '.join(PUNCH_COMMANDS)}", help=f"type of time entry; this can be {', '.join(PUNCH_COMMANDS)}",
default="punch_in", default="punch_in",
choices=PUNCH_COMMANDS) choices=PUNCH_COMMANDS)
parser_punch.add_argument("-s", help=f"timestamp in format `{SIMPLE_DATETIME_FORMAT_HUMAN}`, `{SIMPLE_TIME_FORMAT}`" parser_punch.add_argument("-s",
f" or `now`", default="now") help=f"timestamp in format `{DateFormats.SIMPLE_DATETIME.evalue}`, "
f"`{DateFormats.SIMPLE_TIME.evalue}` or `now`", default="now")
# subparser command: `smart-punch` # subparser command: `smart-punch`
parser_smart_punch = subparsers.add_parser("smart-punch", parser_smart_punch = subparsers.add_parser("smart-punch",
@ -55,8 +56,8 @@ def run():
"entries; this command tries to detect your last action and will " "entries; this command tries to detect your last action and will "
"create entries in to following order: " "create entries in to following order: "
"punch_in -> break_start -> break_end -> punch_out") "punch_in -> break_start -> break_end -> punch_out")
parser_smart_punch.add_argument("-s", help=f"timestamp in format `{SIMPLE_DATETIME_FORMAT_HUMAN}`," parser_smart_punch.add_argument("-s", help=f"timestamp in format `{DateFormats.SIMPLE_DATETIME.evalue}`,"
f" `{SIMPLE_TIME_FORMAT}` or `now`", default="now") f" `{DateFormats.SIMPLE_TIME.evalue}` or `now`", default="now")
args = parser.parse_args() args = parser.parse_args()
@ -80,8 +81,8 @@ def run():
punch_datetime = parse_user_time_input(args.s) punch_datetime = parse_user_time_input(args.s)
logger.info("running `{}` with date `{}` and time `{}`".format( logger.info("running `{}` with date `{}` and time `{}`".format(
args.t, args.t,
punch_datetime.strftime(SIMPLE_DATE_FORMAT), punch_datetime.strftime(DateFormats.SIMPLE_DATE.value),
punch_datetime.strftime(SIMPLE_TIME_FORMAT), punch_datetime.strftime(DateFormats.SIMPLE_TIME.value),
)) ))
getattr(tb, args.t)(punch_datetime) getattr(tb, args.t)(punch_datetime)
elif args.subparser_name == "smart-punch": elif args.subparser_name == "smart-punch":

@ -1,13 +1,32 @@
COMING_ENTRY_CODE_ID = 16 from enum import Enum
LEAVING_ENTRY_CODE_ID = 32 from types import DynamicClassAttribute
BREAK_START_ENTRY_CODE_ID = 48
BREAK_END_ENTRY_CODE_ID = 64
PUNCH_COMMANDS = ("punch_in", "punch_out", "break_start", "break_end")
SIMPLE_DATETIME_FORMAT = "%Y-%m-%dT%H:%M" class PunchCodes(Enum):
SIMPLE_DATETIME_FORMAT_HUMAN = "%%Y-%%m-%%dT%%H:%%M" COMING = 16
SIMPLE_DATE_FORMAT = "%Y-%m-%d" LEAVING = 32
SIMPLE_TIME_FORMAT = "%H:%M" BREAK_START = 48
BREAK_END = 64
class DateFormats(Enum):
SIMPLE_DATETIME = "%Y-%m-%dT%H:%M"
FULL_DATETIME = "%Y-%m-%dT%H:%M:%S"
SIMPLE_DATE = "%Y-%m-%d"
SIMPLE_TIME = "%H:%M"
"""
Return escaped version of value if value is string.
"%" will be escaped -> "%%"
"""
@DynamicClassAttribute
def evalue(self):
if isinstance(self._value_, (str,)):
return self._value_.replace("%", "%%")
return self._value_
PUNCH_COMMANDS = ("punch_in", "punch_out", "break_start", "break_end")
USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0" USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0"

@ -1,3 +1,5 @@
import datetime
from PyQt5 import QtGui from PyQt5 import QtGui
from PyQt5.QtCore import QTimer, QObject, pyqtSignal, QThread from PyQt5.QtCore import QTimer, QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel
@ -14,7 +16,10 @@ class Worker(QObject):
self.timebot = timebot self.timebot = timebot
def run(self): def run(self):
self.sendmsg.emit(self.timebot.status()) hours_present = self.timebot.get_hours_present()
hours_present = hours_present - datetime.timedelta(microseconds=hours_present.microseconds)
self.sendmsg.emit(f"STATUS:\n{self.timebot.status()}\n"
f"HOURS_PRESENT:\n{hours_present}")
self.finished.emit() self.finished.emit()
@ -44,7 +49,7 @@ class TimebotMain(QWidget):
self.status_timer = QTimer() self.status_timer = QTimer()
self.status_timer.timeout.connect(self.update_status) self.status_timer.timeout.connect(self.update_status)
self.status_timer.start(5000) self.status_timer.start(29999)
self.update_status_running = False self.update_status_running = False
self.update_status() self.update_status()

@ -3,12 +3,11 @@ import getpass
import logging import logging
import pickle import pickle
import sys import sys
from typing import Union
import requests as requests import requests as requests
from timebot.constants import COMING_ENTRY_CODE_ID, LEAVING_ENTRY_CODE_ID, BREAK_START_ENTRY_CODE_ID, \ from timebot.constants import PunchCodes, DateFormats, USER_AGENT
BREAK_END_ENTRY_CODE_ID, SIMPLE_DATE_FORMAT, SIMPLE_TIME_FORMAT, SIMPLE_DATETIME_FORMAT, \
USER_AGENT
package_logger = logging.getLogger(__name__) package_logger = logging.getLogger(__name__)
@ -21,20 +20,21 @@ class TimeParseError(Exception):
def parse_user_time_input(stamp: str) -> datetime.datetime: def parse_user_time_input(stamp: str) -> datetime.datetime:
errors = [] errors = []
try: try:
package_logger.debug(f"trying to parse date with format \"{SIMPLE_DATETIME_FORMAT}\"") package_logger.debug(f"trying to parse date with format \"{DateFormats.SIMPLE_DATETIME.value}\"")
return datetime.datetime.strptime(stamp, SIMPLE_DATETIME_FORMAT) return datetime.datetime.strptime(stamp, DateFormats.SIMPLE_DATETIME.value)
except ValueError as e: except ValueError as e:
errors.append(e) errors.append(e)
try: try:
package_logger.debug(f"trying to parse date with format \"{SIMPLE_TIME_FORMAT}\"") package_logger.debug(f"trying to parse date with format \"{DateFormats.SIMPLE_TIME}\"")
t = datetime.datetime.strptime(stamp, SIMPLE_TIME_FORMAT) t = datetime.datetime.strptime(stamp, DateFormats.SIMPLE_TIME.value)
dt = datetime.datetime.now() dt = datetime.datetime.now()
dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) dt = dt.replace(hour=0, minute=0, second=0, microsecond=0)
dt += datetime.timedelta(hours=t.hour, minutes=t.minute) dt += datetime.timedelta(hours=t.hour, minutes=t.minute)
return dt return dt
except ValueError as e: except ValueError as e:
errors.append(e) errors.append(e)
package_logger.error(f"could not parse date with format \"{SIMPLE_DATETIME_FORMAT}\" or \"{SIMPLE_TIME_FORMAT}\"") package_logger.error(f"could not parse date with format \"{DateFormats.SIMPLE_DATETIME.value}\" or"
f" \"{DateFormats.SIMPLE_TIME.value}\"")
raise TimeParseError(errors) raise TimeParseError(errors)
@ -179,8 +179,8 @@ class MobatimeApi:
:param str note: free text note added to the Mobatime entry :param str note: free text note added to the Mobatime entry
:return: requests response object :return: requests response object
""" """
punch_date = punch_datetime.strftime(SIMPLE_DATE_FORMAT) punch_date = punch_datetime.strftime(DateFormats.SIMPLE_DATE.value)
punch_time = punch_datetime.strftime(SIMPLE_TIME_FORMAT) punch_time = punch_datetime.strftime(DateFormats.SIMPLE_TIME.value)
entry_data = { entry_data = {
"periode0Date": punch_date, "periode0Date": punch_date,
"periode0Time": punch_time, "periode0Time": punch_time,
@ -193,7 +193,7 @@ class MobatimeApi:
request = self.session.post(self.baseurl + "Entry/SaveEntry", data=entry_data) request = self.session.post(self.baseurl + "Entry/SaveEntry", data=entry_data)
return request return request
def get_entries(self, entries: int = 10, def get_entries(self, entries: Union[str, None] = 10,
start_date: datetime.datetime = None, start_date: datetime.datetime = None,
end_date: datetime.datetime = None) -> list: end_date: datetime.datetime = None) -> list:
""" """
@ -217,12 +217,12 @@ class MobatimeApi:
filters["filter"]["filters"].append({ filters["filter"]["filters"].append({
"field": "startDate", "field": "startDate",
"operator": "gte", "operator": "gte",
"value": start_date.strftime(SIMPLE_DATETIME_FORMAT), "value": start_date.strftime(DateFormats.SIMPLE_DATETIME.value),
}) })
filters["filter"]["filters"].append({ filters["filter"]["filters"].append({
"field": "endDate", "field": "endDate",
"operator": "lte", "operator": "lte",
"value": end_date.strftime(SIMPLE_DATETIME_FORMAT), "value": end_date.strftime(DateFormats.SIMPLE_DATETIME.value),
}) })
request = self.session.post(self.baseurl + "Entry/GetEntries", json=filters) request = self.session.post(self.baseurl + "Entry/GetEntries", json=filters)
request.raise_for_status() request.raise_for_status()
@ -269,30 +269,30 @@ class TimeBot:
:param datetime.datetime punch_datetime: datetime object :param datetime.datetime punch_datetime: datetime object
:raises: on status code != 2xx :raises: on status code != 2xx
""" """
self.mobatime_api.save_entry(punch_datetime, COMING_ENTRY_CODE_ID, note="da").raise_for_status() self.mobatime_api.save_entry(punch_datetime, PunchCodes.COMING.value, note="da").raise_for_status()
def punch_out(self, punch_datetime: datetime.datetime): def punch_out(self, punch_datetime: datetime.datetime):
""" """
:param datetime.datetime punch_datetime: datetime object :param datetime.datetime punch_datetime: datetime object
:raises: on status code != 2xx :raises: on status code != 2xx
""" """
self.mobatime_api.save_entry(punch_datetime, LEAVING_ENTRY_CODE_ID, note="weg").raise_for_status() self.mobatime_api.save_entry(punch_datetime, PunchCodes.LEAVING.value, note="weg").raise_for_status()
def break_start(self, punch_datetime: datetime.datetime): def break_start(self, punch_datetime: datetime.datetime):
""" """
:param datetime.datetime punch_datetime: datetime object :param datetime.datetime punch_datetime: datetime object
:raises: on status code != 2xx :raises: on status code != 2xx
""" """
self.mobatime_api.save_entry(punch_datetime, BREAK_START_ENTRY_CODE_ID, note="pause").raise_for_status() self.mobatime_api.save_entry(punch_datetime, PunchCodes.BREAK_START.value, note="pause").raise_for_status()
def break_end(self, punch_datetime: datetime.datetime): def break_end(self, punch_datetime: datetime.datetime):
""" """
:param datetime.datetime punch_datetime: datetime object :param datetime.datetime punch_datetime: datetime object
:raises: on status code != 2xx :raises: on status code != 2xx
""" """
self.mobatime_api.save_entry(punch_datetime, BREAK_END_ENTRY_CODE_ID, note="pause ende").raise_for_status() self.mobatime_api.save_entry(punch_datetime, PunchCodes.BREAK_END.value, note="pause ende").raise_for_status()
def smart_punch(self, punch_datetime): def smart_punch(self, punch_datetime) -> None:
last_punch = self.mobatime_api.get_entries( last_punch = self.mobatime_api.get_entries(
1, 1,
start_date=punch_datetime.replace(hour=0, minute=0, second=0, microsecond=0), start_date=punch_datetime.replace(hour=0, minute=0, second=0, microsecond=0),
@ -302,16 +302,16 @@ class TimeBot:
if not last_punch or last_punch[0]["entryNumber"] is None: if not last_punch or last_punch[0]["entryNumber"] is None:
self.logger.debug("could not detect any time entry for today... punching in") self.logger.debug("could not detect any time entry for today... punching in")
method = "punch_in" method = "punch_in"
elif last_punch[0]["entryNumber"] == COMING_ENTRY_CODE_ID: elif last_punch[0]["entryNumber"] == PunchCodes.COMING:
self.logger.debug("your last entry was `punch_in`... starting break") self.logger.debug("your last entry was `punch_in`... starting break")
method = "break_start" method = "break_start"
elif last_punch[0]["entryNumber"] == BREAK_START_ENTRY_CODE_ID: elif last_punch[0]["entryNumber"] == PunchCodes.BREAK_START:
self.logger.debug("your last entry was `break_start`... ending break") self.logger.debug("your last entry was `break_start`... ending break")
method = "break_end" method = "break_end"
elif last_punch[0]["entryNumber"] == BREAK_END_ENTRY_CODE_ID: elif last_punch[0]["entryNumber"] == PunchCodes.BREAK_END:
self.logger.debug("your last entry was `break_end`... punching out") self.logger.debug("your last entry was `break_end`... punching out")
method = "punch_out" method = "punch_out"
elif last_punch[0]["entryNumber"] == LEAVING_ENTRY_CODE_ID: elif last_punch[0]["entryNumber"] == PunchCodes.LEAVING:
self.logger.error("your last entry was `punch_out`... punching in again with this command is not supported") self.logger.error("your last entry was `punch_out`... punching in again with this command is not supported")
sys.exit(1) sys.exit(1)
if method is None: if method is None:
@ -320,12 +320,12 @@ class TimeBot:
exit(1) exit(1)
self.logger.info("running `{}` with date `{}` and time `{}`".format( self.logger.info("running `{}` with date `{}` and time `{}`".format(
method, method,
punch_datetime.strftime(SIMPLE_DATE_FORMAT), punch_datetime.strftime(DateFormats.SIMPLE_DATE),
punch_datetime.strftime(SIMPLE_TIME_FORMAT), punch_datetime.strftime(DateFormats.SIMPLE_TIME),
)) ))
getattr(self, method)(punch_datetime) getattr(self, method)(punch_datetime)
def status(self): def status(self) -> str:
tracking_data = self.mobatime_api.get_tracking_data() tracking_data = self.mobatime_api.get_tracking_data()
account_info = self.mobatime_api.get_account_information() account_info = self.mobatime_api.get_account_information()
ret = "Tracking Info:\n" ret = "Tracking Info:\n"
@ -335,4 +335,14 @@ class TimeBot:
ret += "Account Infos:\n" ret += "Account Infos:\n"
for i in account_info: for i in account_info:
ret += f" {i['accountName']}: {i['value']}\n" ret += f" {i['accountName']}: {i['value']}\n"
return ret return ret
def get_hours_present(self) -> datetime.timedelta:
now = datetime.datetime.now()
punches = self.mobatime_api.get_entries(
None,
start_date=now.replace(hour=0, minute=0, second=0, microsecond=0),
end_date=now.replace(hour=0, minute=0, second=0, microsecond=0),
)
time_delta = now - datetime.datetime.strptime(punches[0]["dateTime"], DateFormats.FULL_DATETIME.value)
return time_delta

Loading…
Cancel
Save