diff --git a/pycaching/trackable.py b/pycaching/trackable.py index 3ce6499..af09e9c 100644 --- a/pycaching/trackable.py +++ b/pycaching/trackable.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 +from bs4 import BeautifulSoup + from pycaching import errors -from pycaching.util import format_date, lazy_loaded +from pycaching.log import Log +from pycaching.util import format_date, lazy_loaded, parse_date # prefix _type() function to avoid collisions with trackable type _type = type @@ -11,7 +14,20 @@ class Trackable(object): """Represents a trackable with its properties.""" def __init__( - self, geocaching, tid, *, name=None, location=None, owner=None, type=None, description=None, goal=None, url=None + self, + geocaching, + tid, + *, + name=None, + location=None, + owner=None, + type=None, + description=None, + goal=None, + url=None, + origin=None, + releaseDate=None, + lastTBLogs=None ): self.geocaching = geocaching if tid is not None: @@ -30,6 +46,12 @@ def __init__( self.type = type if url is not None: self.url = url + if origin is not None: + self.origin = origin + if releaseDate is not None: + self.releaseDate = releaseDate + if lastTBLogs is not None: + self.lastTBLogs = lastTBLogs self._log_page_url = None self._kml_url = None @@ -98,6 +120,8 @@ def location(self): @location.setter def location(self, location): + if location is not None: + location = location.strip() self._location = location @property @@ -166,6 +190,75 @@ def get_KML(self): self.load() # fills self._kml_url return self.geocaching._request(self._kml_url, expect="raw").text + @property + @lazy_loaded + def origin(self): + """The trackable origin. + + :type: :class:`str` + """ + return self._origin + + @origin.setter + def origin(self, origin): + self._origin = origin.strip() + + @property + @lazy_loaded + def originCountry(self): + """The trackable origin country. + + :type: :class:`str` + """ + if "," in self._origin: # there is a state and a country + return self._origin.rsplit(", ", 1)[1] + else: # only country or no value + return self._origin + + @property + @lazy_loaded + def originState(self): + """The trackable origin state. + + :type: :class:`str` + """ + if "," in self._origin: # there is a state and a country + return self._origin.rsplit(", ", 1)[0] + else: # only country or no value + return "" + + @property + @lazy_loaded + def releaseDate(self): + """The trackable releaseDate. + + :type: :class:`str` + """ + return self._releaseDate + + @releaseDate.setter + def releaseDate(self, releaseDate): + if releaseDate is not None: + try: + self._releaseDate = parse_date(releaseDate) + except Exception: + self._releaseDate = "" + else: + self._releaseDate = "" + + @property + @lazy_loaded + def lastTBLogs(self): + """The trackable lastLogs. + + :type: :class:`str` + """ + return self._lastTBLogs + + @lastTBLogs.setter + def lastTBLogs(self, lastTBLogs): + self._lastTBLogs = lastTBLogs + def load(self): """Load all possible details about the trackable. @@ -190,20 +283,83 @@ def load(self): self.tid = root.find("span", "CoordInfoCode").text self.name = root.find(id="ctl00_ContentBody_lbHeading").text self.type = root.find(id="ctl00_ContentBody_BugTypeImage").get("alt") - self.owner = root.find(id="ctl00_ContentBody_BugDetails_BugOwner").text - self.goal = root.find(id="TrackableGoal").text - self.description = root.find(id="TrackableDetails").text - self._kml_url = root.find(id="ctl00_ContentBody_lnkGoogleKML").get("href") + bugDetails = root.find(id="ctl00_ContentBody_BugDetails_BugOwner") + if bugDetails is not None: + self.owner = root.find(id="ctl00_ContentBody_BugDetails_BugOwner").text + else: + self.owner = "" + tbGoal = root.find(id="TrackableGoal") + if tbGoal is not None: + self.goal = root.find(id="TrackableGoal").text + else: + self.goal = "" + tbDescription = root.find(id="TrackableDetails") + if tbDescription is not None: + self.description = root.find(id="TrackableDetails").text + else: + self.description = "" + tbKml = root.find(id="ctl00_ContentBody_lnkGoogleKML") + if tbKml is not None: + self._kml_url = root.find(id="ctl00_ContentBody_lnkGoogleKML").get("href") + bugOrigin = root.find(id="ctl00_ContentBody_BugDetails_BugOrigin") + if bugOrigin is not None: + self.origin = root.find(id="ctl00_ContentBody_BugDetails_BugOrigin").text + else: + self.origin = "" + tbReleaseDate = root.find(id="ctl00_ContentBody_BugDetails_BugReleaseDate") + if tbReleaseDate is not None: + self.releaseDate = root.find(id="ctl00_ContentBody_BugDetails_BugReleaseDate").text + else: + self.releaseDate = "" # another Groundspeak trick... inconsistent relative / absolute URL on one page - self._log_page_url = "/track/" + root.find(id="ctl00_ContentBody_LogLink")["href"] + logLink = root.find(id="ctl00_ContentBody_LogLink") + if logLink is not None: + self._log_page_url = "/track/" + root.find(id="ctl00_ContentBody_LogLink")["href"] location_raw = root.find(id="ctl00_ContentBody_BugDetails_BugLocation") - location_url = location_raw.get("href", "") + if location_raw is not None: + location_url = location_raw.get("href", "") + else: + location_url = "" if "cache_details" in location_url: self.location = location_url else: - self.location = location_raw.text + if location_raw is not None: + self.location = location_raw.text + else: + self.location = "" + + # Load logs which have been already loaded by that request into log object + lastTBLogsTmp = [] + soup = BeautifulSoup(str(root), "html.parser") # Parse the HTML as a string + table = soup.find("table", {"class": "TrackableItemLogTable Table"}) # Grab log table + if table is not None: # handle no logs eg when TB is not active + for row in table.find_all("tr"): + if "BorderTop" in row["class"]: + header = row.find("th") # there should only be one + tbLogType = header.img["title"] + tbLogDate = parse_date(header.get_text().replace(" ", "").strip()) + tbLogOwnerRow = row.find("td") # we need the first one + tbLogOwner = tbLogOwnerRow.a.get_text().strip() + tbLogGUIDRow = row.findAll("td")[2] # we the third one + tbLogGUID = ( + tbLogGUIDRow.a["href"].strip().replace("https://www.geocaching.com/track/log.aspx?LUID=", "") + ) + if "BorderBottom" in row["class"]: + logRow = row.find("td") # there should only be one + tbLogText = logRow.div.get_text().strip() + # create and fill log object + lastTBLogsTmp.append( + Log( + uuid=tbLogGUID, + type=tbLogType, + text=tbLogText, + visited=tbLogDate, + author=tbLogOwner, + ) + ) + self.lastTBLogs = lastTBLogsTmp def _load_log_page(self): """Load a logging page for this trackable. diff --git a/pycaching/util.py b/pycaching/util.py index 9ef7863..0692d2a 100644 --- a/pycaching/util.py +++ b/pycaching/util.py @@ -76,6 +76,10 @@ def parse_date(raw): "%d.%b.%Y", "%b/%d/%Y", "%d %b %y", + "%d %B %Y", + "%B %d, %Y", + "%A, %B %d, %Y", + "%A, %d %B %Y", ) for pattern in patterns: