diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..82bfbbb
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,10 @@
+# Github token in order to let the code automaticly download Geco driver for windows.
+GH_TOKEN=""
+
+BASE_URL="https://live.timeclock365.com/login"
+TIME_CLOCK365_USERNAME=""
+TIME_CLOCK365_PASSWORD=""
+SHIFT_START_TIME=""
+SHIFT_END_TIME=""
+HEADLESS=True # set to True if you wish firefox wan't open on your desktop when the script is called.
+OPERATIONAL=True # Set to True only if you really wants to punch in and save your shifts.
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/auto-timeclock.iml b/.idea/auto-timeclock.iml
new file mode 100644
index 0000000..a80bf12
--- /dev/null
+++ b/.idea/auto-timeclock.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..65bfaf8
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..fee43b8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..c8d457c
--- /dev/null
+++ b/main.py
@@ -0,0 +1,200 @@
+import os
+import logging
+import traceback
+from time import sleep
+from sys import platform
+from time import strftime
+from dotenv import load_dotenv
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.firefox.options import Options
+from selenium.webdriver.support.ui import WebDriverWait
+from webdriver_manager.firefox import GeckoDriverManager
+from selenium.webdriver.support import expected_conditions as EC
+
+
+load_dotenv(".env")
+BASE_URL = os.getenv("BASE_URL")
+USERNAME = os.getenv("TIME_CLOCK365_USERNAME")
+PASSWORD = os.getenv("TIME_CLOCK365_PASSWORD")
+os.environ["GH_TOKEN"] = os.getenv("GH_TOKEN")
+SHIFT_START_TIME = os.getenv("SHIFT_START_TIME")
+SHIFT_END_TIME = os.getenv("SHIFT_END_TIME")
+HEADLESS = True if os.getenv("HEADLESS") == 'True' else False
+OPERATIONAL = True if os.getenv("OPERATIONAL") == 'True' else False
+
+
+def reporter(exception):
+ pass
+
+
+def init():
+ """
+ Init a Selenium base on the OS and loads a web page using .env BASE_URL argument.
+
+ :return webdriver: initiated webdriver with Firefox page loaded with BASE_URL page.
+ :rtype: WebDriver
+ """
+ logging.info("Initializing Browser")
+ try:
+ opt = Options()
+ opt.headless = HEADLESS
+
+ # Check running OS in order to start session correctly.
+ if "win32" in platform:
+ web_page = webdriver.Firefox(options=opt, executable_path=GeckoDriverManager().install())
+ else:
+ web_page = webdriver.Firefox(options=opt)
+ web_page.get(BASE_URL)
+
+ except Exception as e:
+ reporter("Failed to init FireFox client and get to the page.")
+ logging.info(f"Caught and exception while Initializing Browser \n {e}")
+ print(str(traceback.print_exc()) + "\nFailed to init FireFox client and get to the page.")
+ # web_page.close()
+
+ else:
+ logging.info("Finish Initializing Browser")
+ return web_page
+
+
+def validate_field_write(element, field_content):
+ """validating if field is filed with the right data, returned true if success.
+
+ :param element: WebDriver element that should be inspected.
+ :param field_content: The original content the operation trying to insert to field.
+ :type element: WebDriver
+ :type field_content: str
+ :rtype: bool
+ """
+ logging.debug(f"""validating values element: {element.get_attribute("Value")} field_content: {field_content}""")
+ return element.get_attribute("Value") == field_content
+
+
+def navigate_to_time_card(web_page):
+ """
+ Gets already logged in live.timeclock365.com panel and only navigates to the time card page.
+
+ :param web_page: WebDriver - already logged-in Firefox page.
+ :return: None
+ """
+ logging.info("Navigating to time card started.")
+ try:
+ WebDriverWait(web_page, 15).until(EC.presence_of_element_located((By.CLASS_NAME, "first"))).click()
+ timecard_elem = WebDriverWait(web_page, 15).until(EC.presence_of_all_elements_located((By.LINK_TEXT, "דיווח נוכחות")))[1]
+ timecard_elem.click()
+ except Exception as e:
+ logging.info(f"Caught and exception while Navigating to time card.\n {e}")
+ reporter("Field to navigate to timecard page")
+ print(str(e) + "\nField to navigate to timecard page")
+ # web_page.close()
+
+ logging.info("Finish Navigating to time card.")
+
+
+def punch_in(web_page):
+ """
+ Gets session on time card page finds punch in and out elements.
+
+ :param web_page: WebDriver - Time card Firefox page.
+ :type web_page: WebDriver
+ :rtype: None
+ """
+ logging.info("Started to punch in time shifts.")
+ try:
+ # Finding the start and end shift fields.
+ punch_create_elem = web_page.find_elements(By.CLASS_NAME, "sonata-medium-date")
+ except Exception as e:
+ logging.info(f"Caught an exception while punch in time shifts. \n {e}")
+ reporter("Can't find start and end shift fields.")
+ print(str(e) + "start and end shift fields")
+ else:
+ # Filling shift start time
+ punch_create_elem[0].clear()
+ punch_create_elem[0].send_keys(strftime("%d.%m.20%y, ") + SHIFT_START_TIME)
+
+ # Filling shift end time
+ punch_create_elem[1].clear()
+ punch_create_elem[1].send_keys(strftime("%d.%m.20%y, ") + SHIFT_END_TIME)
+
+ # Click on the submit button to punch in if OPERATIONAL set to True.
+ if OPERATIONAL:
+ logging.info("Saving shifts by clicking on the save button.")
+ web_page.find_element(By.NAME, "btn_create_and_list").click()
+ logging.info("Finish Saving shifts.")
+
+
+def login(web_page):
+ """
+ Gets Firefox page loaded at BASE_URL and tries to log in to live.timeclock.com
+
+ using the Username and Password form .env file.
+
+ :param web_page: WebDriver - Firefox page loaded at BASE_URL.
+ :type web_page: WebDriver
+ :raise Exception: undefinable or unloaded elements.
+ :returns: None
+ :rtype: None
+ """
+
+ # Handling possible exception when not finding username field.
+ logging.info("Start login to timecloick365.com")
+ try:
+ user_elem = web_page.find_element(By.NAME, "username")
+ except Exception as e:
+ logging.info(f"Caught and exception while log in \n {e}")
+ reporter("Failed to find username field, closing the script")
+ print(str(e) + "Failed to find username field, closing the script")
+ # web_page.close()
+
+ else:
+ for i in range(5):
+ if not validate_field_write(user_elem, USERNAME):
+ user_elem.send_keys(USERNAME)
+ else:
+ user_elem.send_keys(Keys.ENTER)
+ break
+ else:
+ logging.info(f"Caught an exception while login: unable to fill the username field.")
+ raise Exception("Error: Unable to fill username field.")
+
+ # Handling possible exception when finding password field, upon finding filing the password
+ try:
+ pass_elem = WebDriverWait(web_page, 10, poll_frequency=1).until(EC.presence_of_element_located((By.NAME, "password")))
+ sleep(0.5)
+ except Exception as e:
+ logging.info(f"Failed to find password field, closing the script, maybe invalid username \n {e}")
+ reporter(f"Failed to find password field, closing the script, maybe invalid username \n {e}")
+ print(str(e) + "Failed to find password field, closing the script, maybe invalid username")
+ # web_page.close()
+
+ else:
+ for i in range(5):
+ if not validate_field_write(pass_elem, PASSWORD):
+ pass_elem.send_keys(PASSWORD)
+ else:
+ web_page.find_element(By.CLASS_NAME, "login-page__submit").click()
+ break
+ else:
+ logging.info("Caught an exception while login: unable to fill the username field.")
+ raise Exception("Error: Unable to fill password field.")
+ logging.info("Finish login.")
+
+
+def main():
+ logging.basicConfig(filename='./app.log', filemode="w",
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
+ logging.info('Starting Auto Punch In Script.')
+
+ web_page = init()
+ login(web_page)
+ navigate_to_time_card(web_page)
+ punch_in(web_page)
+ web_page.close()
+ logging.info("Task finished.")
+ print("Finish punch in.")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..f60d9fe
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,23 @@
+async-generator==1.10
+attrs==21.4.0
+certifi==2022.6.15
+cffi==1.15.0
+charset-normalizer==2.0.12
+cryptography==37.0.2
+h11==0.13.0
+idna==3.3
+outcome==1.2.0
+pycparser==2.21
+pyOpenSSL==22.0.0
+PySocks==1.7.1
+python-dotenv==0.20.0
+requests==2.28.0
+selenium==4.2.0
+sniffio==1.2.0
+sortedcontainers==2.4.0
+soupsieve==2.3.2.post1
+trio==0.21.0
+trio-websocket==0.9.2
+urllib3==1.26.9
+webdriver-manager==3.7.0
+wsproto==1.1.0