mobatime cmd line util
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
timebot/timebot.py

252 lines
9.7 KiB

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)