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 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":

@ -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"

@ -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()

@ -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
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