Skip to content

more fields and logs from trackable page #192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 165 additions & 9 deletions pycaching/trackable.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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("&nbsp", "").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.
Expand Down
4 changes: 4 additions & 0 deletions pycaching/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down