import argparse import configparser import datetime import getpass import logging import sys import requests as requests from constants import COMING_ENTRY_CODE_ID, LEAVING_ENTRY_CODE_ID, PUNCH_COMMANDS, BREAK_START_ENTRY_CODE_ID, \ BREAK_END_ENTRY_CODE_ID logger = logging.getLogger() logging.basicConfig(level=logging.INFO) class TimeBot: def __init__(self, baseurl: str, user: str, password: str): self.logger = logging.getLogger(self.__class__.__name__) self.baseurl = self._sanitize_baseurl(baseurl) self.user = user self.password = password self._session = None self._current_user = None @staticmethod def _sanitize_baseurl(baseurl: str): if baseurl.endswith("/"): return baseurl return baseurl + "/" @property def session(self): """ Return requests session object and auto login if necessary. :return: requests session :raises on any http error """ if self._session is None: self._session = requests.Session() request = self._session.get(self.baseurl + "Employee/GetEmployeeList") if 400 <= request.status_code < 500: self._login(self._session) else: request.raise_for_status() return self._session def _login(self, session): """ Obtain session cookie. :raises: on status code != 2xx """ login_data = { "username": self.user, "password": self.password, } request = session.post(self.baseurl + "Account/LogOn", data=login_data) request.raise_for_status() def add_entry(self, punch_date: str, punch_time: str, entry_code: int, note: str = None) -> requests.Response: """ Add mobatime entry. :param str punch_date: date string in format: ``DD.MM.YYYY`` :param str punch_time: time string in format: ``hh:mm`` :param int entry_code: entry type code :param str note: free text note added to the Mobatime entry :return: requests response object """ entry_data = { "periode0Date": punch_date, "periode0Time": punch_time, "selectedEntryCode": entry_code, "selectedPeriodType": 0, "employeeId": self.get_current_user_id(), } if note: entry_data["note"] = note request = self.session.post(self.baseurl + "Entry/SaveEntry", data=entry_data) return request def list_employees(self): """ List all employees which are obviously in the same team as you. :return: list of employees """ request = self.session.get(self.baseurl + "Employee/GetEmployees") request.raise_for_status() return request.json() def _get_user_profile_for_current_user(self): """ Returns all user information for the current user. :return: all user information as dict """ request = self.session.get(self.baseurl + "Employee/GetUserProfileForCurrentUser") request.raise_for_status() return request.json() @property def current_user(self): """ Returns all user information for the current user. :return: all user information as dict """ if self._current_user is None: self._current_user = self._get_user_profile_for_current_user() return self._current_user def get_current_user_id(self): """ Get the current users id. :return: user id """ return self.current_user["employee"]["id"] def get_entries(self, entries: int = 10, start_date: datetime.datetime = None, end_date: datetime.datetime = None): """ List time entries. :param int entries: number of entries to return :param datetime.datetime start_date: start date of request; by default ``end_date`` - 10 days :param datetime.datetime end_date: end date of request; usually the more recent date :return: """ if not end_date: end_date = datetime.datetime.now() if not start_date: start_date = end_date - datetime.timedelta(days=10) filters = {"take": entries, "skip": 0, "page": 1, "pageSize": entries, "sort": [{"field": "dateTime", "dir": "desc"}], "filter": {"logic": "and", "filters": [ {"field": "employeeId", "operator": "eq", "value": self.get_current_user_id()}, {"field": "errorText", "operator": "eq", "value": "all"}]}} filters["filter"]["filters"].append({ "field": "startDate", "operator": "gte", "value": start_date.strftime("%Y-%m-%dT%H:%M:%S"), }) filters["filter"]["filters"].append({ "field": "endDate", "operator": "lte", "value": end_date.strftime("%Y-%m-%dT%H:%M:%S"), }) request = self.session.post(self.baseurl + "Entry/GetEntries", json=filters) request.raise_for_status() return request.json()["data"] def punch_in(self, punch_in_date: str, punch_in_time: str): """ :param str punch_in_date: date string in format: ``DD.MM.YYYY`` :param str punch_in_time: time string in format: ``hh:mm`` :raises: on status code != 2xx """ self.add_entry(punch_in_date, punch_in_time, COMING_ENTRY_CODE_ID, note="da").raise_for_status() def punch_out(self, punch_out_date: str, punch_out_time: str): """ :param str punch_out_date: date string in format: ``DD.MM.YYYY`` :param str punch_out_time: time string in format: ``hh:mm`` :raises: on status code != 2xx """ self.add_entry(punch_out_date, punch_out_time, LEAVING_ENTRY_CODE_ID, note="weg").raise_for_status() def break_start(self, punch_date: str, punch_time: str): """ :param str punch_date: date string in format: ``DD.MM.YYYY`` :param str punch_time: time string in format: ``hh:mm`` :raises: on status code != 2xx """ self.add_entry(punch_date, punch_time, BREAK_START_ENTRY_CODE_ID, note="pause").raise_for_status() def break_end(self, punch_date: str, punch_time: str): """ :param str punch_date: date string in format: ``DD.MM.YYYY`` :param str punch_time: time string in format: ``hh:mm`` :raises: on status code != 2xx """ self.add_entry(punch_date, punch_time, BREAK_END_ENTRY_CODE_ID, note="pause ende").raise_for_status() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-v", help="enable debug logging", action="store_true") parser.add_argument("-u", help="mobatime login user", required=True) parser.add_argument("-p", help="mobatime login user password", default=None) parser.add_argument("-c", help="config file", default="timebot.ini") subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') # subparser command: `punch` parser_punch = subparsers.add_parser("punch", help="use this command to punch in, punch out, or create break entries") parser_punch.add_argument("-t", help="type of time entry; this can be `punch_in`, `punch_out`, `break_start`, `break_end`", default="punch_in", choices=PUNCH_COMMANDS) parser_punch.add_argument("-s", help="timestamp in format `DD.MM.YYYY_hh:mm` or `now`", default="now") # 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="start date filter in format `DD.MM.YYYY_hh:mm` (default: now - 10days; unset for default)") parser_list_entries.add_argument("--end-date", help="end date filter in format `DD.MM.YYYY_hh:mm` (default: now; unset for default)") parser_list_entries.add_argument("--items", help="max items to request per page", default=20, type=int) args = parser.parse_args() if args.v: logger.setLevel(logging.DEBUG) password = args.p if password is None: password = getpass.getpass("Enter your password: ") config = configparser.ConfigParser() config.read(args.c) tb = TimeBot(baseurl=config["general"]["baseurl"], user=args.u, password=password) if args.subparser_name == "punch": if args.s == "now": now = datetime.datetime.now() punch_date = now.strftime("%d.%m.%Y") punch_time = now.strftime("%H:%M") else: punch_date = args.s.split("_")[0] punch_time = args.s.split("_")[1] logger.info("running `{}` with date `{}` and time `{}`".format(args.t, punch_date, punch_time)) getattr(tb, args.t)(punch_date, punch_time) elif args.subparser_name == "list-entries": end_date = None if args.end_date: end_date = datetime.datetime.strptime(args.end_date, "%d.%m.%Y_%H:%M") start_date = None if args.start_date: start_date = datetime.datetime.strptime(args.start_date, "%d.%m.%Y_%H:%M") data = tb.get_entries(entries=args.items, start_date=start_date, end_date=end_date) for i in data: print("Entry: {} - DateTime: {} - Note: {}".format(i["entryName"], i["dateTime"], i["note"])) else: logger.error("Noting done... dunno what you want!") sys.exit(1) sys.exit(0)