From 2ab0d6d9202eabc722959b381e3d97e05c70ba5c Mon Sep 17 00:00:00 2001 From: Maximilian Zettler Date: Wed, 9 Mar 2022 17:45:43 +0100 Subject: [PATCH] wip: add get_hours_present method --- timebot/app.py | 23 ++++++++-------- timebot/constants.py | 37 +++++++++++++++++++------- timebot/gui.py | 9 +++++-- timebot/timebot.py | 62 +++++++++++++++++++++++++------------------- 4 files changed, 83 insertions(+), 48 deletions(-) diff --git a/timebot/app.py b/timebot/app.py index ba68fcd..b174523 100644 --- a/timebot/app.py +++ b/timebot/app.py @@ -5,7 +5,7 @@ import logging import os 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 logger = logging.getLogger() @@ -24,18 +24,18 @@ def run(): subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') # 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` - parser_status = subparsers.add_parser("gui", help="start qt5 gui") + subparsers.add_parser("gui", help="start qt5 gui") # subparser command: `list-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", - 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)") 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)") 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)}", default="punch_in", choices=PUNCH_COMMANDS) - parser_punch.add_argument("-s", help=f"timestamp in format `{SIMPLE_DATETIME_FORMAT_HUMAN}`, `{SIMPLE_TIME_FORMAT}`" - f" or `now`", default="now") + parser_punch.add_argument("-s", + help=f"timestamp in format `{DateFormats.SIMPLE_DATETIME.evalue}`, " + f"`{DateFormats.SIMPLE_TIME.evalue}` or `now`", default="now") # subparser command: `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 " "create entries in to following order: " "punch_in -> break_start -> break_end -> punch_out") - parser_smart_punch.add_argument("-s", help=f"timestamp in format `{SIMPLE_DATETIME_FORMAT_HUMAN}`," - f" `{SIMPLE_TIME_FORMAT}` or `now`", default="now") + parser_smart_punch.add_argument("-s", help=f"timestamp in format `{DateFormats.SIMPLE_DATETIME.evalue}`," + f" `{DateFormats.SIMPLE_TIME.evalue}` or `now`", default="now") args = parser.parse_args() @@ -80,8 +81,8 @@ def run(): punch_datetime = parse_user_time_input(args.s) logger.info("running `{}` with date `{}` and time `{}`".format( args.t, - punch_datetime.strftime(SIMPLE_DATE_FORMAT), - punch_datetime.strftime(SIMPLE_TIME_FORMAT), + punch_datetime.strftime(DateFormats.SIMPLE_DATE.value), + punch_datetime.strftime(DateFormats.SIMPLE_TIME.value), )) getattr(tb, args.t)(punch_datetime) elif args.subparser_name == "smart-punch": diff --git a/timebot/constants.py b/timebot/constants.py index 95bbf40..4ff9570 100644 --- a/timebot/constants.py +++ b/timebot/constants.py @@ -1,13 +1,32 @@ -COMING_ENTRY_CODE_ID = 16 -LEAVING_ENTRY_CODE_ID = 32 -BREAK_START_ENTRY_CODE_ID = 48 -BREAK_END_ENTRY_CODE_ID = 64 +from enum import Enum +from types import DynamicClassAttribute -PUNCH_COMMANDS = ("punch_in", "punch_out", "break_start", "break_end") -SIMPLE_DATETIME_FORMAT = "%Y-%m-%dT%H:%M" -SIMPLE_DATETIME_FORMAT_HUMAN = "%%Y-%%m-%%dT%%H:%%M" -SIMPLE_DATE_FORMAT = "%Y-%m-%d" -SIMPLE_TIME_FORMAT = "%H:%M" +class PunchCodes(Enum): + COMING = 16 + LEAVING = 32 + 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" diff --git a/timebot/gui.py b/timebot/gui.py index 43a4e7a..1978f78 100644 --- a/timebot/gui.py +++ b/timebot/gui.py @@ -1,3 +1,5 @@ +import datetime + from PyQt5 import QtGui from PyQt5.QtCore import QTimer, QObject, pyqtSignal, QThread from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel @@ -14,7 +16,10 @@ class Worker(QObject): self.timebot = timebot 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() @@ -44,7 +49,7 @@ class TimebotMain(QWidget): self.status_timer = QTimer() 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() diff --git a/timebot/timebot.py b/timebot/timebot.py index 98cd380..e8c77f2 100644 --- a/timebot/timebot.py +++ b/timebot/timebot.py @@ -3,12 +3,11 @@ import getpass import logging import pickle import sys +from typing import Union import requests as requests -from timebot.constants import COMING_ENTRY_CODE_ID, LEAVING_ENTRY_CODE_ID, BREAK_START_ENTRY_CODE_ID, \ - BREAK_END_ENTRY_CODE_ID, SIMPLE_DATE_FORMAT, SIMPLE_TIME_FORMAT, SIMPLE_DATETIME_FORMAT, \ - USER_AGENT +from timebot.constants import PunchCodes, DateFormats, USER_AGENT package_logger = logging.getLogger(__name__) @@ -21,20 +20,21 @@ class TimeParseError(Exception): def parse_user_time_input(stamp: str) -> datetime.datetime: errors = [] try: - package_logger.debug(f"trying to parse date with format \"{SIMPLE_DATETIME_FORMAT}\"") - return datetime.datetime.strptime(stamp, SIMPLE_DATETIME_FORMAT) + package_logger.debug(f"trying to parse date with format \"{DateFormats.SIMPLE_DATETIME.value}\"") + return datetime.datetime.strptime(stamp, DateFormats.SIMPLE_DATETIME.value) except ValueError as e: errors.append(e) try: - package_logger.debug(f"trying to parse date with format \"{SIMPLE_TIME_FORMAT}\"") - t = datetime.datetime.strptime(stamp, SIMPLE_TIME_FORMAT) + package_logger.debug(f"trying to parse date with format \"{DateFormats.SIMPLE_TIME}\"") + t = datetime.datetime.strptime(stamp, DateFormats.SIMPLE_TIME.value) dt = datetime.datetime.now() dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) dt += datetime.timedelta(hours=t.hour, minutes=t.minute) return dt except ValueError as 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) @@ -179,8 +179,8 @@ class MobatimeApi: :param str note: free text note added to the Mobatime entry :return: requests response object """ - punch_date = punch_datetime.strftime(SIMPLE_DATE_FORMAT) - punch_time = punch_datetime.strftime(SIMPLE_TIME_FORMAT) + punch_date = punch_datetime.strftime(DateFormats.SIMPLE_DATE.value) + punch_time = punch_datetime.strftime(DateFormats.SIMPLE_TIME.value) entry_data = { "periode0Date": punch_date, "periode0Time": punch_time, @@ -193,7 +193,7 @@ class MobatimeApi: request = self.session.post(self.baseurl + "Entry/SaveEntry", data=entry_data) return request - def get_entries(self, entries: int = 10, + def get_entries(self, entries: Union[str, None] = 10, start_date: datetime.datetime = None, end_date: datetime.datetime = None) -> list: """ @@ -217,12 +217,12 @@ class MobatimeApi: filters["filter"]["filters"].append({ "field": "startDate", "operator": "gte", - "value": start_date.strftime(SIMPLE_DATETIME_FORMAT), + "value": start_date.strftime(DateFormats.SIMPLE_DATETIME.value), }) filters["filter"]["filters"].append({ "field": "endDate", "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.raise_for_status() @@ -269,30 +269,30 @@ class TimeBot: :param datetime.datetime punch_datetime: datetime object :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): """ :param datetime.datetime punch_datetime: datetime object :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): """ :param datetime.datetime punch_datetime: datetime object :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): """ :param datetime.datetime punch_datetime: datetime object :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( 1, 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: self.logger.debug("could not detect any time entry for today... punching 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") 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") 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") 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") sys.exit(1) if method is None: @@ -320,12 +320,12 @@ class TimeBot: exit(1) self.logger.info("running `{}` with date `{}` and time `{}`".format( method, - punch_datetime.strftime(SIMPLE_DATE_FORMAT), - punch_datetime.strftime(SIMPLE_TIME_FORMAT), + punch_datetime.strftime(DateFormats.SIMPLE_DATE), + punch_datetime.strftime(DateFormats.SIMPLE_TIME), )) getattr(self, method)(punch_datetime) - def status(self): + def status(self) -> str: tracking_data = self.mobatime_api.get_tracking_data() account_info = self.mobatime_api.get_account_information() ret = "Tracking Info:\n" @@ -335,4 +335,14 @@ class TimeBot: ret += "Account Infos:\n" for i in account_info: ret += f" {i['accountName']}: {i['value']}\n" - return ret \ No newline at end of file + 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