def check_page(self, title: str): """ Make sure we are on the right page by checking the title""" try: assert title in self.browser.driver.title except AssertionError: raise NameError(_("Page with wrong title")) print80(_("Connected to Wunderground"))
def wait_until_page_is_loaded(self): try: WebDriverWait(self.browser.driver, self.TIMEOUT).until( ec.presence_of_element_located( (By.ID, "hourly-forecast-table"))) print80(_("Page is ready")) except TimeoutException: raise TimeoutException(_("Page took too much time to load"))
def get_atm_pressure_at_station(self): _elem = self.browser.driver.find_element_by_xpath( '//*[@id="inner-content"]/div[3]/div[2]/div/div[1]' "/div[1]/lib-additional-conditions/lib-item-box/div/" "div[2]/div/div[1]/div[2]/lib-display-unit/span/span[1]") self.P_INITIAL = float(_elem.text) print80( self.register_info( _("Current atmospheric pressure : {} hPa (ISA={:0.1f}m)"). format(self.P_INITIAL, isa.altitude(pressure=self.P_INITIAL)))) return self.P_INITIAL
def switch_to_metric(self): print80(_("Switching to metric")) self.browser.driver.find_element_by_id("wuSettings").click() self.browser.driver.find_element_by_css_selector( "[title^='Switch to Metric'").click() try: WebDriverWait(self.browser.driver, self.TIMEOUT_LONG).until( ec.text_to_be_present_in_element( (By.ID, "hourly-forecast-table"), "hPa")) except TimeoutException: raise TimeoutException(_("Unable to switch to metric"))
def get_obs_time(self): _elem = self.browser.driver.find_element(By.XPATH, '//*[@id="app-root-state"]') _st = search( "obsTimeLocal&q;:&q;(....-..-.. ..:..:..)&q;", _elem.get_attribute("innerHTML"), ) if _st is not None: return datetime.strptime(_st.group(1), "%Y-%m-%d %H:%M:%S") else: print80(self.register_error(_("Observation time not found."))) return datetime.now()
def hourly_forecast_url(self): if self.GEOLOCATION_ALWAYS_ON or (self.MISSING_LATLONG and not self.OVERRIDE_URL_EXISTS): self.browser.go_to(webpage=self.ANY_HTTPS_PAGE, geolocation=True) position = self.browser.get_lat_lon() self.save_lat_lon(pos=position) self.browser.quit() # decreased precision (3 digits instead of 7) to partially preserve anonymity _hourly_forecast_url = self.GEOLOCATED_URL + "{:.3f},{:.3f}".format( position["latitude"], position["longitude"]) elif self.OVERRIDE_URL_EXISTS: print80(_("Using override URL")) _hourly_forecast_url = self.OVERRIDE_URL elif not self.MISSING_LATLONG: if args.latitude is None: print80( _("Using Lat/Lon found in {}").format( self.CONFIG_FILENAME)) print80(_("Latitude: {}").format(self.LATITUDE)) print80(_("Longitude: {}").format(self.LONGITUDE)) print() # decreased precision (3 digits instead of 7) to partially preserve anonymity _hourly_forecast_url = self.GEOLOCATED_URL + "{:.3f},{:.3f}".format( self.LATITUDE, self.LONGITUDE) else: # if everything fails, an example url _hourly_forecast_url = "https://www.wunderground.com/hourly/ca/montreal/IMONTR15" return _hourly_forecast_url
def get_station_elevation(self): try: _elem = self.browser.driver.find_element( By.XPATH, '//*[@id="inner-content"]/div[2]' "/lib-city-header/div[1]/div/span/span/strong", ) self.ELEVATION = int(_elem.text) except NoSuchElementException: print80( self.register_error( _("Elevation not found. Assuming it to be zero"))) self.ELEVATION = 0 print80( self.register_info( _("Weather station elevation : {}m (ISA={:7.2f}hPa)").format( self.ELEVATION, isa.pressure(altitude=self.ELEVATION))))
def get_station_name(self): try: _elem = self.browser.driver.find_element_by_xpath( '//*[@id="inner-content"]/div[2]/lib-city-header/div[1]/div/div/a[1]' ) _st = search("^-?[0-9]* (.*)$", _elem.text) self.STATION_NAME = _st.group(1).strip() except NoSuchElementException: print80(self.register_error(_("Station name not found"))) if self.STATION_NAME is None or self.STATION_NAME == "STATION": try: _elem = self.browser.driver.find_element_by_xpath( '//*[@id="inner-content"]/div[2]/lib-city-header/div[1]/div/h1' ) self.STATION_NAME = _elem.text[:20].lstrip(", ") + "..." except NoSuchElementException: self.STATION_NAME = _("UNKNOWN") logging.info(self.STATION_NAME) print() print80(colored(self.STATION_NAME, attrs=["bold"]))
def send_to_slack(self, _fix_hour): _txt = self.result.display_table() _title = "{}-{:}".format(self.STATION_NAME, _fix_hour.strftime("%Y%m%d-%H%M")).replace( " ", "_") _comment = "{} ({}m)\n\n".format(self.STATION_NAME, self.ELEVATION) try: print80( _("Sending timetable to Slack channel {}").format(args.slack)) _response = self.slack.files_upload( content=_txt, channels=args.slack, title=_title, filename=_title + ".txt", initial_comment=_comment, ) assert _response["ok"] print80( _("Sending {} to Slack channel {}").format( program.GRAPH_FILENAME, args.slack)) _response = self.slack.files_upload( file=program.GRAPH_FILENAME, channels=args.slack, title=_title, filename=_title + "." + program.GRAPH_FILENAME.split(".")[-1], initial_comment=_comment, ) assert _response["ok"] except AssertionError: print80(self.register_error(_("Sending to Slack failed")))
def save_lat_lon(self, pos): self.cfg.set(self.CS, self.LATITUDE_T, str(pos["latitude"])) self.cfg.set(self.CS, self.LONGITUDE_T, str(pos["longitude"])) self.save_ini() print80(_("Latitude : {:.7f}").format(pos["latitude"])) print80(_("Longitude : {:.7f}").format(pos["longitude"])) print80(_("Accuracy : {}m").format(pos["accuracy"])) logging.info("{:.7f} {:.7f} ±{}m".format(pos["latitude"], pos["longitude"], pos["accuracy"])) print()
def start_console(self): """ Open OS console for stdout :return: """ system("title {} {} (Python {})".format(self.NAME, self.VERSION, python_version())) print80( colored( "{} {}".format(self.NAME, self.VERSION), attrs=["bold"], )) print80(self.DESCRIPTION) print() major, minor, patchlevel = map(int, python_version_tuple()) if major != 3 or minor < 6: print80( _("{} works best with Python version 3.6 and above. Please consider updating." ).format(self.NAME)) print()
def __init__(self, fullname: str, version: str, description: str, shortname: str): """ :param fullname: program name :param version: program version :param description: short description :param shortname: short name, mainly for logs """ self.NAME = fullname self.SHORTNAME = shortname self.VERSION = version self.DESCRIPTION = description self.STATION_NAME = None self.ELEVATION = None self.P_INITIAL = None self.start_console() # starts logging self.LOG_FILENAME = self.SHORTNAME + ".log" logging.basicConfig( filename=self.LOG_FILENAME, level=logging.INFO, filemode="w", format="%(asctime)s %(levelname)s : %(message)s", datefmt="%Y-%m-%d %H:%M", ) self.result = PredictionTable() self.forecast = Forecast() self.slack = slack.WebClient(token=environ["SLACK_API_TOKEN"]) # reads configuration file and recreates missing values self.CONFIG_FILENAME = "config.ini" self.CS = "USER SETTINGS" self.cfg = ConfigParser() self.cfg.read(self.CONFIG_FILENAME) if self.CS not in self.cfg.sections(): print80(_("Regenerating {}").format(self.CONFIG_FILENAME)) print() self.cfg.add_section(self.CS) self.TIMEOUT_T = "short timeout" self.TIMEOUT = int(self.cfg.get(self.CS, self.TIMEOUT_T, fallback="5")) self.TIMEOUT_LONG_T = "long timeout" self.TIMEOUT_LONG = int( self.cfg.get(self.CS, self.TIMEOUT_LONG_T, fallback="10")) self.GEOLOCATION_ALWAYS_ON_T = "geolocation always on" self.GEOLOCATION_ALWAYS_ON = bool( int( self.cfg.get(self.CS, self.GEOLOCATION_ALWAYS_ON_T, fallback="0"))) self.WAIT_FOR_KEY_T = "press any key" self.WAIT_FOR_KEY = bool( int(self.cfg.get(self.CS, self.WAIT_FOR_KEY_T, fallback="1"))) self.PAUSE = (not args.no_key) and self.WAIT_FOR_KEY self.OVERRIDE_URL_T = "override url" self.OVERRIDE_URL_ = self.cfg.get(self.CS, self.OVERRIDE_URL_T, fallback="") if args.override_url is not None: self.OVERRIDE_URL = args.override_url else: self.OVERRIDE_URL = self.OVERRIDE_URL_ self.OVERRIDE_URL_EXISTS = self.OVERRIDE_URL is not None and self.OVERRIDE_URL.startswith( "https://www.wunderground.com/hourly/") self.ANY_HTTPS_PAGE_T = "https page" self.ANY_HTTPS_PAGE = self.cfg.get(self.CS, self.ANY_HTTPS_PAGE_T, fallback="https://blank.org") self.GEOLOCATED_URL_T = "wunderground hourly url" self.GEOLOCATED_URL = self.cfg.get( self.CS, self.GEOLOCATED_URL_T, fallback="https://www.wunderground.com/hourly/ca/location/", ) self.GRAPH_FILENAME_T = "autosave png-pdf-eps filename" self.GRAPH_FILENAME = self.cfg.get(self.CS, self.GRAPH_FILENAME_T, fallback="graph.png") self.GRAPH_DPI_T = "autosave dpi" self.GRAPH_DPI = int( self.cfg.get(self.CS, self.GRAPH_DPI_T, fallback="600")) self.GRAPH_ORIENTATION_T = "autosave orientation" self.GRAPH_ORIENTATION = self.cfg.get(self.CS, self.GRAPH_ORIENTATION_T, fallback="landscape") self.GRAPH_PAPERTYPE_T = "autosave papertype" self.GRAPH_PAPERTYPE = self.cfg.get(self.CS, self.GRAPH_PAPERTYPE_T, fallback="letter") self.VERBOSE_T = "verbose" self.VERBOSE_ = bool( int(self.cfg.get(self.CS, self.VERBOSE_T, fallback="0"))) self.VERBOSE = self.VERBOSE_ or args.verbose self.SHOW_X_HOURS_T = "display x hours" self.SHOW_X_HOURS = max( int(self.cfg.get(self.CS, self.SHOW_X_HOURS_T, fallback="6")), 1) self.MIN_HOURS_T = "minimum hours" self.MIN_HOURS = max( int(self.cfg.get(self.CS, self.MIN_HOURS_T, fallback="8")), self.SHOW_X_HOURS, ) self.save_ini() # save immediately to renew all required values # optional values that can be missing self.LATITUDE_T = "latitude" self.LONGITUDE_T = "longitude" self.LATITUDE = None self.LONGITUDE = None if args.latitude is not None: self.LATITUDE = args.latitude elif self.cfg.has_option(self.CS, self.LATITUDE_T): self.LATITUDE = float(self.cfg.get(self.CS, self.LATITUDE_T)) if args.longitude is not None: self.LONGITUDE = args.longitude elif self.cfg.has_option(self.CS, self.LONGITUDE_T): self.LONGITUDE = float(self.cfg.get(self.CS, self.LONGITUDE_T)) self.MISSING_LATLONG = self.LATITUDE is None or self.LONGITUDE is None self.browser = ChromeBrowser()
first_day = datetime.today() same_day = 1 if datetime.now( ).hour == 23: # https://github.com/Wlodarski/DR-Altimeter/issues/6 first_day += timedelta(days=1) same_day = 0 last_day = first_day + timedelta(hours=program.MIN_HOURS) nth_days = range(0, same_day + nb_date_changes(first_day, last_day)) dates = [first_day.date() + timedelta(days=day) for day in nth_days] first_page = True for d in dates: date_str = d.strftime("%Y-%m-%d") url = hourly_forecast_url + "/date/" + date_str if program.VERBOSE: print80(url) if first_page: program.browser.go_to(webpage=url, hidden=True) program.check_page( title="Hourly Weather Forecast | Weather Underground") program.wait_until_page_is_loaded() program.switch_to_metric() first_page = False else: program.click_next() if program.STATION_NAME is None: program.get_station_name() if program.ELEVATION is None: program.get_station_elevation() if program.P_INITIAL is None: