|
|
@ -3,6 +3,7 @@ import configparser |
|
|
|
import datetime |
|
|
|
import datetime |
|
|
|
import getpass |
|
|
|
import getpass |
|
|
|
import logging |
|
|
|
import logging |
|
|
|
|
|
|
|
import pickle |
|
|
|
import sys |
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
import requests as requests |
|
|
|
import requests as requests |
|
|
@ -16,11 +17,14 @@ logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimeBot: |
|
|
|
class TimeBot: |
|
|
|
def __init__(self, baseurl: str, user: str, password: str): |
|
|
|
def __init__(self, baseurl: str, user: str, password: str = None, ask_for_password: bool = False, |
|
|
|
|
|
|
|
save_session: bool = False): |
|
|
|
self.logger = logging.getLogger(self.__class__.__name__) |
|
|
|
self.logger = logging.getLogger(self.__class__.__name__) |
|
|
|
self.baseurl = self._sanitize_baseurl(baseurl) |
|
|
|
self.baseurl = self._sanitize_baseurl(baseurl) |
|
|
|
self.user = user |
|
|
|
self.user = user |
|
|
|
self.password = password |
|
|
|
self.password = password |
|
|
|
|
|
|
|
self._ask_for_password = ask_for_password |
|
|
|
|
|
|
|
self._save_session = save_session |
|
|
|
self._session = None |
|
|
|
self._session = None |
|
|
|
self._current_user = None |
|
|
|
self._current_user = None |
|
|
|
|
|
|
|
|
|
|
@ -40,19 +44,29 @@ class TimeBot: |
|
|
|
""" |
|
|
|
""" |
|
|
|
if self._session is None: |
|
|
|
if self._session is None: |
|
|
|
self._session = requests.Session() |
|
|
|
self._session = requests.Session() |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
self._load_session_cookies(self._session) |
|
|
|
|
|
|
|
except FileNotFoundError as e: |
|
|
|
|
|
|
|
self.logger.error(e) |
|
|
|
|
|
|
|
raise |
|
|
|
request = self._session.get(self.baseurl + "Employee/GetEmployeeList") |
|
|
|
request = self._session.get(self.baseurl + "Employee/GetEmployeeList") |
|
|
|
if 400 <= request.status_code < 500: |
|
|
|
if 400 <= request.status_code < 500: |
|
|
|
self._login(self._session) |
|
|
|
self._login(self._session) |
|
|
|
|
|
|
|
self._save_session_cookies(self._session) |
|
|
|
else: |
|
|
|
else: |
|
|
|
request.raise_for_status() |
|
|
|
request.raise_for_status() |
|
|
|
return self._session |
|
|
|
return self._session |
|
|
|
|
|
|
|
|
|
|
|
def _login(self, session): |
|
|
|
def _login(self, session: requests.Session): |
|
|
|
""" |
|
|
|
""" |
|
|
|
Obtain session cookie. |
|
|
|
Obtain session cookie. |
|
|
|
|
|
|
|
|
|
|
|
:raises: on status code != 2xx |
|
|
|
:raises: on status code != 2xx |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
if self.password is None and self._ask_for_password: |
|
|
|
|
|
|
|
self.password = self._get_password() |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
raise Exception("could not obtain password") |
|
|
|
login_data = { |
|
|
|
login_data = { |
|
|
|
"username": self.user, |
|
|
|
"username": self.user, |
|
|
|
"password": self.password, |
|
|
|
"password": self.password, |
|
|
@ -60,6 +74,35 @@ class TimeBot: |
|
|
|
request = session.post(self.baseurl + "Account/LogOn", data=login_data) |
|
|
|
request = session.post(self.baseurl + "Account/LogOn", data=login_data) |
|
|
|
request.raise_for_status() |
|
|
|
request.raise_for_status() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def _get_password(): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Ask the user for his password. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:return: the users password |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
return getpass.getpass("Enter your password: ") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _save_session_cookies(self, session: requests.Session): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Save the session cookies as pickle file. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param requests.Session session: the requests session to extract the cookies from |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
with open(".kekse", "wb") as f: |
|
|
|
|
|
|
|
if self._save_session: |
|
|
|
|
|
|
|
pickle.dump(requests.utils.dict_from_cookiejar(session.cookies), f) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_session_cookies(self, session: requests.Session): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Load the session cookies from the pickle file and updates the given session. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param requests.Session session: the requests session which will be updated with the loaded cookies |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
with open(".kekse", "rb") as f: |
|
|
|
|
|
|
|
if self._save_session: |
|
|
|
|
|
|
|
session.cookies.update(requests.utils.cookiejar_from_dict(pickle.load(f))) |
|
|
|
|
|
|
|
|
|
|
|
def add_entry(self, punch_datetime: datetime.datetime, entry_code: int, note: str = None) -> requests.Response: |
|
|
|
def add_entry(self, punch_datetime: datetime.datetime, entry_code: int, note: str = None) -> requests.Response: |
|
|
|
""" |
|
|
|
""" |
|
|
|
Add mobatime entry. |
|
|
|
Add mobatime entry. |
|
|
@ -191,6 +234,7 @@ if __name__ == '__main__': |
|
|
|
parser.add_argument("-u", help="mobatime login user", required=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("-p", help="mobatime login user password", default=None) |
|
|
|
parser.add_argument("-c", help="config file", default="timebot.ini") |
|
|
|
parser.add_argument("-c", help="config file", default="timebot.ini") |
|
|
|
|
|
|
|
parser.add_argument("--save-cookies", help="save auth cookies to `./.kekse`", action="store_true", default=False) |
|
|
|
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') |
|
|
|
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') |
|
|
|
|
|
|
|
|
|
|
|
# subparser command: `punch` |
|
|
|
# subparser command: `punch` |
|
|
@ -200,7 +244,8 @@ if __name__ == '__main__': |
|
|
|
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}` or `now`", default="now") |
|
|
|
parser_punch.add_argument("-s", help=f"timestamp in format `{SIMPLE_DATETIME_FORMAT_HUMAN}` or `now`", |
|
|
|
|
|
|
|
default="now") |
|
|
|
|
|
|
|
|
|
|
|
# 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") |
|
|
@ -225,14 +270,11 @@ if __name__ == '__main__': |
|
|
|
if args.v: |
|
|
|
if args.v: |
|
|
|
logger.setLevel(logging.DEBUG) |
|
|
|
logger.setLevel(logging.DEBUG) |
|
|
|
|
|
|
|
|
|
|
|
password = args.p |
|
|
|
|
|
|
|
if password is None: |
|
|
|
|
|
|
|
password = getpass.getpass("Enter your password: ") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config = configparser.ConfigParser() |
|
|
|
config = configparser.ConfigParser() |
|
|
|
config.read(args.c) |
|
|
|
config.read(args.c) |
|
|
|
|
|
|
|
|
|
|
|
tb = TimeBot(baseurl=config["general"]["baseurl"], user=args.u, password=password) |
|
|
|
tb = TimeBot(baseurl=config["general"]["baseurl"], user=args.u, password=args.p, ask_for_password=args.save_cookies, |
|
|
|
|
|
|
|
save_session=True) |
|
|
|
if args.subparser_name == "punch": |
|
|
|
if args.subparser_name == "punch": |
|
|
|
if args.s == "now": |
|
|
|
if args.s == "now": |
|
|
|
punch_datetime = datetime.datetime.now() |
|
|
|
punch_datetime = datetime.datetime.now() |
|
|
@ -249,16 +291,16 @@ if __name__ == '__main__': |
|
|
|
last_punch = tb.get_entries(1, now.replace(hour=0, minute=0, second=0, microsecond=0)) |
|
|
|
last_punch = tb.get_entries(1, now.replace(hour=0, minute=0, second=0, microsecond=0)) |
|
|
|
method = None |
|
|
|
method = None |
|
|
|
if not last_punch: |
|
|
|
if not last_punch: |
|
|
|
logger.info("could not detect any time entry for today... punching in") |
|
|
|
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"] == COMING_ENTRY_CODE_ID: |
|
|
|
logger.info("your last entry was `punch_in`... starting break") |
|
|
|
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"] == BREAK_START_ENTRY_CODE_ID: |
|
|
|
logger.info("your last entry was `break_start`... ending break") |
|
|
|
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"] == BREAK_END_ENTRY_CODE_ID: |
|
|
|
logger.info("your last entry was `break_end`... punching out") |
|
|
|
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"] == LEAVING_ENTRY_CODE_ID: |
|
|
|
logger.error("your last entry was `punch_out`... punching in again with this command is not supported") |
|
|
|
logger.error("your last entry was `punch_out`... punching in again with this command is not supported") |
|
|
@ -267,6 +309,11 @@ if __name__ == '__main__': |
|
|
|
logger.error("hit an unknown situation... detection failed; run with `-v` for more info") |
|
|
|
logger.error("hit an unknown situation... detection failed; run with `-v` for more info") |
|
|
|
logger.debug(f"last entry was: {last_punch}") |
|
|
|
logger.debug(f"last entry was: {last_punch}") |
|
|
|
exit(1) |
|
|
|
exit(1) |
|
|
|
|
|
|
|
logger.info("running `{}` with date `{}` and time `{}`".format( |
|
|
|
|
|
|
|
method, |
|
|
|
|
|
|
|
now.strftime(SIMPLE_DATE_FORMAT), |
|
|
|
|
|
|
|
now.strftime(SIMPLE_TIME_FORMAT), |
|
|
|
|
|
|
|
)) |
|
|
|
getattr(tb, method)(now) |
|
|
|
getattr(tb, method)(now) |
|
|
|
elif args.subparser_name == "list-entries": |
|
|
|
elif args.subparser_name == "list-entries": |
|
|
|
end_date = None |
|
|
|
end_date = None |
|
|
|