def _send_notification(self, site): site_item = site.get_item_name() logger.info("Sending notification for '%s'", site_item) self._notifier.notify( site.get_url(friendly=True), "Watcher signaled for item '{item}'".format(item=site_item), site.get_url())
def refresh_sites(self): logger.info("Refreshing sites") for handle in self._driver.window_handles: self._driver.switch_to_window(handle) self._driver.refresh() self._switch_to_main_window()
def __init__(self, browser, interval, notifier): logger.info("Initializing watcher") super().__init__() self._shutdown = False self._browser = browser self._interval = interval self._notifier = notifier
def __init__(self, remote_notifications=False): self._local_notifier = LocalNotify() if remote_notifications: self._remote_notifier = RemoteNotify() logger.info( "Registering a new Remote Push Notification (RPN) endpoint") remote_notify_info = self._remote_notifier.register() self._display_remote_notify_info(str(remote_notify_info)) else: self._remote_notifier = None
def _display_remote_notify_info(cls, remote_notify_info): if os.name == 'nt': # Windows cmd/powershell does not display QR code properly - stripping it off remote_notify_info = remote_notify_info[:remote_notify_info.index( "Or scan this QR code")] logger.info( """\n\n****************** REMOTE PUSH NOTIFICATIONS ********************\n %s \nNOTE: iOS and Safari NOT supported \n*****************************************************************\n""", remote_notify_info )
def _evaluate_baseline(self, site, n_elements): site_notify_type = site.get_notify_type() element_baseline = None if (n_elements == 0 and site_notify_type == NotifyOnType.APPEAR) or \ (n_elements > 0 and site_notify_type == NotifyOnType.DISAPPEAR): logger.info("Tracking '%s:%s[%s]' for site '%s'", site.get_watch_type().value, site.get_text(), site_notify_type.value, site.get_url(friendly=True)) element_baseline = n_elements logger.debug("'%s': [baseline=%d]", element_baseline, site.get_url(friendly=True)) else: logger.warning( "Unable to establish an accurate baseline with '%s:%s[%s]' for site '%s' - " "Action may have already occurred?", site.get_watch_type().value, site.get_text(), site_notify_type.value, site.get_url(friendly=True)) site.set_element_baseline(element_baseline) return element_baseline
def scrape_sites_by_xpath(self): logger.info("Scraping sites for data...") scrape_results = [] for i in range(len(self._driver.window_handles)): self._driver.switch_to_window(self._driver.window_handles[i]) site = self._sites[i] n_elements = 0 for xpath in site.get_xpaths(): tmp_n_elements = len( self._driver.find_elements_by_xpath(xpath)) if tmp_n_elements and n_elements > 0: # Multiple elements with same text - too ambiguous to refine search raise ValueError( "Multiple elements found containing '{text}' - is ambiguous" .format(text=site.get_text())) elif n_elements == 0: n_elements = tmp_n_elements scrape_results.append((site, n_elements)) self._switch_to_main_window() return scrape_results
def start(self): """ Starts the pyww application. Returns ------- None """ signal.signal(signal.SIGINT, self._shut_down) logger.info(self._ascii_art) try: self._watcher = Watcher( Chrome(site_parser.parse(self._sites), headless=(ENVIRON_DEBUG_KEY not in environ)), self._interval, Notifier(remote_notifications=self._remote_notifications)) self._watcher.start() except ValueError as err: logger.error(str(err)) finally: if self._watcher: self._wait_on_watcher()
def load_sites(self): if not self._sites: return False for i in range(len(self._sites)): site = self._sites[i] logger.info("Loading '%s' for '%s'", site.get_url(friendly=True), site.get_item_name()) if i == 0: self._driver.get(site.get_url()) else: self._driver.execute_script( "window.open('{url}', '_blank');".format( url=site.get_url())) # Make sure the driver window handles are updated before loading another site expected_handles = i + 1 while len(self._driver.window_handles) != expected_handles: logger.debug("Waiting for %d window handles from driver...", expected_handles) sleep(0.1) self._switch_to_main_window() return True
def _track_changes(self, scrape_results): for site, n_elements in scrape_results: site_url = site.get_url(friendly=True) site_notify_type = site.get_notify_type() element_baseline = site.get_element_baseline() if element_baseline is None: element_baseline = self._evaluate_baseline(site, n_elements) # Unable to track changes if a baseline is not established if element_baseline is None: logger.warning("Skipping tracking '%s'...", site_url) continue if (n_elements > element_baseline and site_notify_type == NotifyOnType.APPEAR) or \ (n_elements < element_baseline and site_notify_type == NotifyOnType.DISAPPEAR): # Action occurred - alert user logger.info("ALERT '%s:%s[%s]' in '%s'", site.get_watch_type().value, site.get_text(), site_notify_type.value, site_url) logger.debug("'%s': [baseline=%d, [n_elements=%d]", site_url, element_baseline, n_elements) self._send_notification(site) else: # No changes found yet logger.info("No changes found in '%s'", site_url)
def _shut_down(self, *_): logger.info("Shutdown signaled for pyww...") self._watcher.shut_down()
def _sleep_on_interval(self): logger.info("Waiting %ds before refresh", self._interval) for _ in range(self._interval): if self._shutdown: break sleep(1)
def _teardown(self): logger.info("Tearing down watcher") self._browser.close()