From 23c41237ffc4479a0c43a0e8e15cd47418f6079a Mon Sep 17 00:00:00 2001 From: Blallo Date: Mon, 14 Jan 2019 12:11:17 +0100 Subject: [PATCH] login/logout mostly working. --- LICENSE | 49 ++++++++------------- README.rst | 33 ++++---------- bot_z/bot_z.py | 116 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 126 insertions(+), 72 deletions(-) diff --git a/LICENSE b/LICENSE index ca89857..53ff67b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,35 +1,22 @@ +GLWTS(Good Luck With That Shit) Public License +Copyright (c) Every-fucking-one, except the Author -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +The author has absolutely no fucking clue what the code in this project does. +It might just fucking work or not, there is no third option. - A bot to easen the daily routine with zucchetti virtual badge. - Copyleft 2019 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +Everyone is permitted to copy, distribute, modify, merge, sell, publish, +sublicense or whatever fuck they want with this software but at their OWN RISK. + GOOD LUCK WITH THAT SHIT PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION + +0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE A +FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for or held +responsible. + +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Good luck and Godspeed. diff --git a/README.rst b/README.rst index 2309933..1b0f817 100644 --- a/README.rst +++ b/README.rst @@ -3,38 +3,21 @@ Bot_Z ===== -.. image:: https://img.shields.io/pypi/v/bot_z.svg - :target: https://pypi.python.org/pypi/bot_z - -.. image:: https://img.shields.io/travis/lbarcaroli/bot_z.svg - :target: https://travis-ci.org/lbarcaroli/bot_z - -.. image:: https://readthedocs.org/projects/bot-z/badge/?version=latest - :target: https://bot-z.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status - -.. image:: https://pyup.io/repos/github/lbarcaroli/bot_z/shield.svg - :target: https://pyup.io/repos/github/lbarcaroli/bot_z/ - :alt: Updates - - A bot to easen the daily routine with zucchetti virtual badge. -* Free software: GNU General Public License v3 -* Documentation: https://bot-z.readthedocs.io. +* Free software: GLWTS public licence. Features -------- -* TODO +* Login/Logout -Credits ---------- - -This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. - -.. _Cookiecutter: https://github.com/audreyr/cookiecutter -.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage +TODO +---- + * Check in/out + * systemd {unit, timer} + * APIs + * ... diff --git a/bot_z/bot_z.py b/bot_z/bot_z.py index 5b1cd25..facf719 100644 --- a/bot_z/bot_z.py +++ b/bot_z/bot_z.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- -"""Main module.""" +""" +Operator is the main object that interacts with the foreign +service. It exposes methods to login, logout and check in and out. +""" +from datetime import datetime, timedelta import logging import os import time @@ -15,10 +19,12 @@ from selenium.common.exceptions import WebDriverException, NoSuchElementExceptio logging.basicConfig( - level=logging.INFO, + level=os.environ.get('BOTZ_LOGLEVEL', logging.INFO), format='%(levelname)s: [%(name)s] -> %(message)s' ) +m_logger = logging.getLogger(__name__) +m_logger.debug("Init at debug") def safely(f: T.Callable) -> T.Callable: def _protection(self, *args, **kwargs): @@ -30,11 +36,43 @@ def safely(f: T.Callable) -> T.Callable: return _protection +def _is_present(driver: wd.Firefox, xpath: str) -> bool: + try: + driver.find_element_by_xpath(xpath) + return True + except NoSuchElementException: + return False + + +def is_present(driver: wd.Firefox, + xpath: str, + timeout: T.Optional[timedelta]=None + ) -> bool: + """ + Helper function. If an element is present in the DOM tree, + returns true. False otherwise. + """ + if timeout is None: + return _is_present(driver, xpath) + + _now = datetime.now() + _elapsed = timedelta(seconds=0) + while _elapsed < timeout: + m_logger.debug("Not yet present: %s", xpath) + if _is_present(driver, xpath): + m_logger.debug("Present: %s", xpath) + return True + time.sleep(0.5) + _elapsed = datetime.now() - _now + return False + + class Operator(wd.Firefox): def __init__( self, base_uri: str, name: str = None, + timeout: int=20, proxy: T.Optional[T.Tuple[str, int]] = None, headless: bool = True, debug: bool = False, @@ -53,6 +91,7 @@ class Operator(wd.Firefox): self.debug = debug self.base_uri = base_uri + self.timeout = timedelta(seconds=timeout) if proxy: self.profile.set_preference('network.proxy.type', 1) @@ -72,15 +111,29 @@ class Operator(wd.Firefox): self._checked_in = False @safely - def do_login(self, user: str, password: str) -> None: + def login(self, user: str, password: str, force: bool=False) -> None: """ Do the login and proceed. """ + if self._logged_in: + self.logger.warning("Already logged in: %s", user) + if not force: + return + self.logger.info("Forcing login: %s", user) # Retrieve login page - #self.get('{}/jsp/login.jsp'.format(self.base_uri)) self.get(self.base_uri) + _correct_url = 'cpccchk' in self.current_url + _now = datetime.now() + _elapsed = timedelta(seconds=0) + while not _correct_url: + self.logger.debug("Not yet on login page: %s", self.current_url) + time.sleep(0.5) + _correct_url = 'cpccchk' in self.current_url + _elapsed = datetime.now() - _now + if _elapsed > self.timeout: + break self.logger.debug("After login get: %s", self.current_url) - time.sleep(3) + time.sleep(1) # Username user_form = self.find_element_by_name('m_cUserName') # Password @@ -91,24 +144,51 @@ class Operator(wd.Firefox): user_form.send_keys(user) pass_form.send_keys(password) do_it = True - if self.debug: - _do_it = input("Really do the login? [y/n]").lower() + if self.debug and not force: + _do_it = input("Really do the login? [y/n] ").lower() do_it = True if _do_it == "y" else False if do_it: - self.logger.debug("Before clicking: %s", self.current_url) login_butt.submit() - self.logger.debug("After clicking: %s", self.current_url) + time.sleep(5) self.logger.debug("Login result: %s", self.title) if 'Routine window' in self.title: + self.logger.debug("Reloading...") self.refresh() - try: - self.find_element_by_xpath('//input[contains(@value, "Accedi")]') - # TODO: reckon a proper timeout mechanism - # based on cookie expire time + self.switch_to.alert.accept() + if is_present(self, '//a[contains(@class, "imgMenu_ctrl")]', self.timeout): self._logged_in = True self.logger.info("Login success for user: %s", user) - except NoSuchElementException as e: - self.logger.error("Login failed: %s", e) + else: + self.logger.error("Login failed: %s", user) + + @safely + def logout(self, user: str, force: bool=False) -> None: + """ + Do the logout. + """ + if not self._logged_in: + self.logger.warning("Not yet logged in for user: %s", user) + if not force: + return + self.logger.info("Forcing logout: %s", user) + # Find the Profile menu and open it + profile_butt = self.find_element_by_xpath('//span[contains(@id, "imgNoPhotoLabel")]') + profile_butt.click() + time.sleep(1) + # Find the logout button + logout_butt = self.find_element_by_xpath('//input[@value="Logout user"]') + logout_butt.click() + if "jsp/usut_wapplogout_portlet.jsp" in self.current_url: + self.logger.info("User successfully logged out: %s", user) + else: + self.logger.warning("Logout failed: %s", user) + + def logged_in(self): + """ + Check if already logged in. Checks if page is '/jsp/home.jsp' + and if login cookie is set (and not expired). + """ + pass @safely def check_in(self, force: bool=False) -> None: @@ -121,6 +201,8 @@ class Operator(wd.Firefox): self.logger.warn("Already logged in!") if not force: return + enter_butt = self.find_element_by_xpath('//input[@value="Entrata"]') + enter_butt.submit() # Click the check in button and change # self._checked_in state in case of success pass @@ -136,9 +218,11 @@ class Operator(wd.Firefox): self.logger.warn("Not yet logged in!") if not force: return + exit_butt = self.find_element_by_xpath('//input[@value="Uscita"]') + exit_butt.submit() # Click the check in button and change # self._checked_in state in case of success pass - def __del__(self): + def __del__(self) -> None: self.quit()