diff --git a/README.md b/README.md index e9e877c..4bdf974 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ A simple command line utility to make mobatime time entries. +# Development + +This is a poetry managed project. For details see: +* https://python-poetry.org/docs/#installation +* https://python-poetry.org/docs/basic-usage/ + # ERRORS ## SSL verify error diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..230ce72 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,286 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.11" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.8.3" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "more-itertools" +version = "8.12.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "typing-extensions" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.8" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "90a881b9873674d1beb1cbae32c605d019b3f2a945780d9fff9f98f2ad26aa02" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, + {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, +] +more-itertools = [ + {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, + {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +typing-extensions = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +urllib3 = [ + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +zipp = [ + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..797f3a8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "timebot" +version = "0.1.0" +description = "" +authors = ["Maximilian Zettler "] + +[tool.poetry.scripts] +timebot = 'timebot.app:run' + +[tool.poetry.dependencies] +python = "^3.6" +requests = "^2.27.1" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_timebot.py b/tests/test_timebot.py new file mode 100644 index 0000000..d0e4a3f --- /dev/null +++ b/tests/test_timebot.py @@ -0,0 +1,5 @@ +from timebot import __version__ + + +def test_version(): + assert __version__ == '0.1.0' diff --git a/timebot.ini.ex b/timebot.ini.ex index 0aeba59..8436d09 100644 --- a/timebot.ini.ex +++ b/timebot.ini.ex @@ -1,2 +1,3 @@ [general] -baseurl=https://host/mobatime/ \ No newline at end of file +baseurl=https://host/mobatime/ +system_ca_store=/etc/ssl/certs/ca-certificates.crt diff --git a/timebot/__init__.py b/timebot/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/timebot/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/timebot/app.py b/timebot/app.py new file mode 100644 index 0000000..d2c2dd7 --- /dev/null +++ b/timebot/app.py @@ -0,0 +1,116 @@ +import argparse +import configparser +import datetime +import logging +import os +import sys + +from timebot.constants import SIMPLE_DATETIME_FORMAT_HUMAN, PUNCH_COMMANDS, SIMPLE_DATETIME_FORMAT, SIMPLE_DATE_FORMAT, \ + SIMPLE_TIME_FORMAT +from timebot.timebot import MobatimeApi, TimeBot + +logger = logging.getLogger() +logging.basicConfig(level=logging.INFO) + + +def run(): + 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") + parser.add_argument("-a", "--use-system-ca-store", help="use system ca store for ssl connections", + action="store_true") + 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') + + # subparser command: `status` + parser_status = subparsers.add_parser("status", help="show your current tracking status") + + # 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}` " + 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}` " + f"(default: now; unset for default)") + parser_list_entries.add_argument("--items", help="max items to request per page", default=20, type=int) + + # 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=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}` or `now`", + default="now") + + # subparser command: `smart-punch` + parser_smart_punch = subparsers.add_parser("smart-punch", + help="use this command to auto punch in, punch out and create break " + "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}` or `now`", + default="now") + + args = parser.parse_args() + + if args.v: + logger.setLevel(logging.DEBUG) + + config = configparser.ConfigParser() + config.read(args.c) + + if args.use_system_ca_store: + os.environ["REQUESTS_CA_BUNDLE"] = config["general"]["system_ca_store"]\ + if "system_ca_store" in config["general"] else "/etc/ssl/certs/ca-certificates.crt" + + ma = MobatimeApi(baseurl=config["general"]["baseurl"], user=args.u, password=args.p, ask_for_password=True, + save_session=args.save_cookies) + tb = TimeBot(mobatime_api=ma) + if args.subparser_name == "punch": + if args.s == "now": + punch_datetime = datetime.datetime.now() + else: + punch_datetime = datetime.datetime.strptime(args.s, SIMPLE_DATETIME_FORMAT) + logger.info("running `{}` with date `{}` and time `{}`".format( + args.t, + punch_datetime.strftime(SIMPLE_DATE_FORMAT), + punch_datetime.strftime(SIMPLE_TIME_FORMAT), + )) + getattr(tb, args.t)(punch_datetime) + elif args.subparser_name == "smart-punch": + if args.s == "now": + punch_datetime = datetime.datetime.now() + else: + punch_datetime = datetime.datetime.strptime(args.s, SIMPLE_DATETIME_FORMAT) + tb.smart_punch(punch_datetime) + elif args.subparser_name == "list-entries": + end_date = None + if args.end_date: + end_date = datetime.datetime.strptime(args.end_date, SIMPLE_DATETIME_FORMAT) + start_date = None + if args.start_date: + start_date = datetime.datetime.strptime(args.start_date, SIMPLE_DATETIME_FORMAT) + data = tb.mobatime_api.get_entries(entries=args.items, start_date=start_date, end_date=end_date) + data.reverse() + for i in data: + print("Entry: {} - DateTime: {} - Note: {}".format(i["entryName"], i["dateTime"], i["note"])) + elif args.subparser_name == "status": + tracking_data = tb.mobatime_api.get_tracking_data() + account_info = tb.mobatime_api.get_account_information() + print("Tracking Info:") + print(f" Current State: {tracking_data['actualState']}") + print(f" Last Booking: {tracking_data['lastBooking']}") + print(f" Last Booking Date: {tracking_data['lastBookingDate']}") + print("Account Infos:") + for i in account_info: + print(f" {i['accountName']}: {i['value']}") + else: + logger.error("Noting done... dunno what you want!") + sys.exit(1) + + sys.exit(0) diff --git a/constants.py b/timebot/constants.py similarity index 100% rename from constants.py rename to timebot/constants.py diff --git a/timebot.py b/timebot/timebot.py similarity index 62% rename from timebot.py rename to timebot/timebot.py index 78d1e3c..a99ded4 100644 --- a/timebot.py +++ b/timebot/timebot.py @@ -1,5 +1,3 @@ -import argparse -import configparser import datetime import getpass import logging @@ -8,12 +6,9 @@ import sys import requests as requests -from constants import COMING_ENTRY_CODE_ID, LEAVING_ENTRY_CODE_ID, PUNCH_COMMANDS, BREAK_START_ENTRY_CODE_ID, \ +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, \ - SIMPLE_DATETIME_FORMAT_HUMAN, USER_AGENT - -logger = logging.getLogger() -logging.basicConfig(level=logging.INFO) + USER_AGENT class MobatimeApi: @@ -49,9 +44,9 @@ class MobatimeApi: try: self._load_session_cookies(self._session) except FileNotFoundError as e: - logger.warning(e) # file does not exist... ignored + self.logger.warning(e) # file does not exist... ignored except (EOFError, pickle.UnpicklingError) as e: - logger.warning(e) # file seems to be corrupt... ignoring + self.logger.warning(e) # file seems to be corrupt... ignoring request = self._session.get(self.baseurl + "Employee/GetEmployeeList") if 400 <= request.status_code < 500: self.logger.debug(f"got error {request.status_code}... trying to log in") @@ -271,127 +266,30 @@ class TimeBot: self.mobatime_api.save_entry(punch_datetime, BREAK_END_ENTRY_CODE_ID, note="pause ende").raise_for_status() def smart_punch(self, punch_datetime): - last_punch = tb.mobatime_api.get_entries(1, punch_datetime.replace(hour=0, minute=0, second=0, microsecond=0)) + last_punch = self.mobatime_api.get_entries(1, punch_datetime.replace(hour=0, minute=0, second=0, microsecond=0)) method = None if not last_punch or last_punch[0]["entryNumber"] is None: - logger.debug("could not detect any time entry for today... punching in") + 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: - logger.debug("your last entry was `punch_in`... starting break") + self.logger.debug("your last entry was `punch_in`... starting break") method = "break_start" elif last_punch[0]["entryNumber"] == BREAK_START_ENTRY_CODE_ID: - logger.debug("your last entry was `break_start`... ending break") + self.logger.debug("your last entry was `break_start`... ending break") method = "break_end" elif last_punch[0]["entryNumber"] == BREAK_END_ENTRY_CODE_ID: - logger.debug("your last entry was `break_end`... punching out") + self.logger.debug("your last entry was `break_end`... punching out") method = "punch_out" 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") + 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: - logger.error("hit an unknown situation... detection failed; run with `-v` for more info") - logger.debug(f"last entry was: {last_punch}") + self.logger.error("hit an unknown situation... detection failed; run with `-v` for more info") + self.logger.debug(f"last entry was: {last_punch}") exit(1) - logger.info("running `{}` with date `{}` and time `{}`".format( + self.logger.info("running `{}` with date `{}` and time `{}`".format( method, punch_datetime.strftime(SIMPLE_DATE_FORMAT), punch_datetime.strftime(SIMPLE_TIME_FORMAT), )) getattr(self, method)(punch_datetime) - - -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") - 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') - - # subparser command: `status` - parser_status = subparsers.add_parser("status", help="show your current tracking status") - - # 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}` " - 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}` " - f"(default: now; unset for default)") - parser_list_entries.add_argument("--items", help="max items to request per page", default=20, type=int) - - # 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=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}` or `now`", - default="now") - - # subparser command: `smart-punch` - parser_smart_punch = subparsers.add_parser("smart-punch", - help="use this command to auto punch in, punch out and create break " - "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}` or `now`", - default="now") - - args = parser.parse_args() - - if args.v: - logger.setLevel(logging.DEBUG) - - config = configparser.ConfigParser() - config.read(args.c) - - ma = MobatimeApi(baseurl=config["general"]["baseurl"], user=args.u, password=args.p, ask_for_password=True, - save_session=args.save_cookies) - tb = TimeBot(mobatime_api=ma) - if args.subparser_name == "punch": - if args.s == "now": - punch_datetime = datetime.datetime.now() - else: - punch_datetime = datetime.datetime.strptime(args.s, SIMPLE_DATETIME_FORMAT) - logger.info("running `{}` with date `{}` and time `{}`".format( - args.t, - punch_datetime.strftime(SIMPLE_DATE_FORMAT), - punch_datetime.strftime(SIMPLE_TIME_FORMAT), - )) - getattr(tb, args.t)(punch_datetime) - elif args.subparser_name == "smart-punch": - if args.s == "now": - punch_datetime = datetime.datetime.now() - else: - punch_datetime = datetime.datetime.strptime(args.s, SIMPLE_DATETIME_FORMAT) - tb.smart_punch(punch_datetime) - elif args.subparser_name == "list-entries": - end_date = None - if args.end_date: - end_date = datetime.datetime.strptime(args.end_date, SIMPLE_DATETIME_FORMAT) - start_date = None - if args.start_date: - start_date = datetime.datetime.strptime(args.start_date, SIMPLE_DATETIME_FORMAT) - data = tb.mobatime_api.get_entries(entries=args.items, start_date=start_date, end_date=end_date) - data.reverse() - for i in data: - print("Entry: {} - DateTime: {} - Note: {}".format(i["entryName"], i["dateTime"], i["note"])) - elif args.subparser_name == "status": - tracking_data = tb.mobatime_api.get_tracking_data() - account_info = tb.mobatime_api.get_account_information() - print("Tracking Info:") - print(f" Current State: {tracking_data['actualState']}") - print(f" Last Booking: {tracking_data['lastBooking']}") - print(f" Last Booking Date: {tracking_data['lastBookingDate']}") - print("Account Infos:") - for i in account_info: - print(f" {i['accountName']}: {i['value']}") - else: - logger.error("Noting done... dunno what you want!") - sys.exit(1) - - sys.exit(0)