Beispiel #1
0
    def load_quick(self):
        """Load basic cache details.

        Use information from geocaching map tooltips. Therefore loading is very quick, but
        the only loaded properties are: `name`, `type`, `state`, `size`, `difficulty`, `terrain`,
        `hidden`, `author`, `favorites` and `pm_only`.

        :raise .LoadError: If cache loading fails (probably because of not existing cache).
        """
        res = self.geocaching._request("http://tiles01.geocaching.com/map.details", params={
            "i": self.wp
        }, expect="json")

        if res["status"] == "failed" or len(res["data"]) != 1:
            msg = res["msg"] if "msg" in res else "Unknown error (probably not existing cache)"
            raise errors.LoadError("Cache {} cannot be loaded: {}".format(self, msg))

        data = res["data"][0]

        # prettify data
        self.name = data["name"]
        self.type = Type.from_string(data["type"]["text"])
        self.state = data["available"]
        self.size = Size.from_string(data["container"]["text"])
        self.difficulty = data["difficulty"]["text"]
        self.terrain = data["terrain"]["text"]
        self.hidden = parse_date(data["hidden"])
        self.author = data["owner"]["text"]
        self.favorites = int(data["fp"])
        self.pm_only = data["subrOnly"]

        logging.debug("Cache loaded: {}".format(self))
Beispiel #2
0
    def _logbook_get_page(self, page=0, per_page=25):
        """Load one page from logbook.

        :param int page: Logbook page to load.
        :param int per_page: Logs per page (used to calculate start index).
        :raise .LoadError: If loading fails.
        """
        res = self.geocaching._request(
            self._urls["logbook"],
            params={
                "tkn":
                self._logbook_token,  # will trigger lazy_loading if needed
                "idx":
                int(page) + 1,  # Groundspeak indexes this from 1 (OMG..)
                "num": int(per_page),
                "decrypt": "true"
            },
            expect="json")

        if res["status"] != "success":
            error_msg = res["msg"] if "msg" in res else "Unknown error"
            raise errors.LoadError(
                "Logbook cannot be loaded: {}".format(error_msg))

        return res["data"]
Beispiel #3
0
    def load(self):
        """Load all possible details about the trackable.

        .. note::
           This method is called automatically when you access a property which isn't yet filled in
           (so-called "lazy loading"). You don't have to call it explicitly.

        :raise .LoadError: If trackable loading fails (probably because of not existing cache).
        """
        # pick url based on what info we have right now
        if hasattr(self, "url"):
            url = self.url
        elif hasattr(self, "_tid"):
            url = "track/details.aspx?tracker={}".format(self._tid)
        else:
            raise errors.LoadError("Trackable lacks info for loading")

        # make request
        root = self.geocaching._request(url)

        # parse data
        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

        location_raw = root.find(id="ctl00_ContentBody_BugDetails_BugLocation")
        location_url = location_raw.get("href")
        if "cache_details" in location_url:
            self.location = location_url
        else:
            self.location = location_raw.text
Beispiel #4
0
    def load(self):
        """Load all possible details about the trackable.

        .. note::
           This method is called automatically when you access a property which isn't yet filled in
           (so-called "lazy loading"). You don't have to call it explicitly.

        :raise .LoadError: If trackable loading fails (probably because of not existing cache).
        """
        # pick url based on what info we have right now
        if hasattr(self, "url"):
            url = self.url
        elif hasattr(self, "_tid"):
            url = "track/details.aspx?tracker={}".format(self._tid)
        else:
            raise errors.LoadError("Trackable lacks info for loading")

        # make request
        root = self.geocaching._request(url)

        # parse data
        self.tid = root.find("span", "CoordInfoCode").text
        self.name = root.find(id="ctl00_ContentBody_lbHeading").text
        try:
            self.type = root.find(
                id="ctl00_ContentBody_BugTypeImage").get("alt")
        except (TypeError, KeyError, AttributeError) as e:
            logging.debug("BugType not found: " + str(e))
            self.type = "Unknown"
            pass
        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")

        # another Groundspeak trick... inconsistent relative / absolute URL on one page
        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 "cache_details" in location_url:
            self.location = location_url
        else:
            self.location = location_raw.text
Beispiel #5
0
    def load(self):
        """Load all possible cache details.

        Use full cache details page. Therefore all possible properties are filled in, but the
        loading is a bit slow.

        If you want to load basic details about a PM only cache, the :class:`.PMOnlyException` is
        still thrown, but avaliable details are filled in. If you know, that the cache you are
        loading is PM only, please consider using :meth:`load_quick` as it will load the same
        details, but quicker.

        .. note::
           This method is called automatically when you access a property which isn't yet filled in
           (so-called "lazy loading"). You don't have to call it explicitly.

        :raise .PMOnlyException: If cache is PM only and current user is basic member.
        :raise .LoadError: If cache loading fails (probably because of not existing cache).
        """
        try:
            # pick url based on what info we have right now
            if hasattr(self, "url"):
                root = self.geocaching._request(self.url)
            elif hasattr(self, "_wp"):
                root = self.geocaching._request("seek/cache_details.aspx", params={"wp": self._wp})
            else:
                raise errors.LoadError("Cache lacks info for loading")
        except errors.Error as e:
            # probably 404 during cache loading - cache not exists
            raise errors.LoadError("Error in loading cache") from e

        # check for PM only caches if using free account
        self.pm_only = root.find("section", "pmo-banner") is not None

        cache_details = root.find(id="ctl00_divContentMain") if self.pm_only else root.find(id="cacheDetails")

        # details also avaliable for basic members for PM only caches -----------------------------

        if self.pm_only:
            self.wp = cache_details.find("li", "li__gccode").text.strip()

            self.name = cache_details.find("h1").text.strip()

            author = cache_details.find(id="ctl00_ContentBody_uxCacheBy").text
            self.author = author[len("A cache by "):]

            # parse cache detail list into a python list
            details = cache_details.find("ul", "ul__hide-details").text.split("\n")

            self.difficulty = float(details[2])

            self.terrain = float(details[5])

            self.size = Size.from_string(details[8])

            self.favorites = int(details[11])
        else:
            # parse from <title> - get first word
            try:
                self.wp = root.title.string.split(" ")[0]
            except:
                raise errors.LoadError
            self.name = cache_details.find("h2").text

            self.author = cache_details("a")[1].text

            size = root.find("div", "CacheSize")

            D_and_T_img = root.find("div", "CacheStarLabels").find_all("img")

            size = size.find("img").get("src")  # size img src
            size = size.split("/")[-1].rsplit(".", 1)[0]  # filename w/o extension
            self.size = Size.from_filename(size)

            self.difficulty, self.terrain = [float(img.get("alt").split()[0]) for img in D_and_T_img]

        type = cache_details.find("img").get("src")  # type img src
        type = type.split("/")[-1].rsplit(".", 1)[0]  # filename w/o extension
        self.type = Type.from_filename(type)

        if self.pm_only:
            raise errors.PMOnlyException()

        # details not avaliable for basic members for PM only caches ------------------------------
        pm_only_warning = root.find("p", "Warning NoBottomSpacing")
        self.pm_only = pm_only_warning and ("Premium Member Only" in pm_only_warning.text) or False

        attributes_widget, inventory_widget, *_ = root.find_all("div", "CacheDetailNavigationWidget")

        hidden = cache_details.find("div", "minorCacheDetails").find_all("div")[1].text
        self.hidden = parse_date(hidden.split(":")[-1])

        self.location = Point.from_string(root.find(id="uxLatLon").text)

        self.state = root.find("ul", "OldWarning") is None

        found = root.find("div", "FoundStatus")
        self.found = found and ("Found It!" or "Attended" in found.text) or False

        attributes_raw = attributes_widget.find_all("img")
        attributes_raw = [_.get("src").split("/")[-1].rsplit("-", 1) for _ in attributes_raw]

        self.attributes = {attribute_name: appendix.startswith("yes") for attribute_name, appendix
                           in attributes_raw if not appendix.startswith("blank")}

        user_content = root.find_all("div", "UserSuppliedContent")
        self.summary = user_content[0].text
        self.description = str(user_content[1])

        self.hint = rot13(root.find(id="div_hint").text.strip())

        favorites = root.find("span", "favorite-value")
        self.favorites = 0 if favorites is None else int(favorites.text)

        self._log_page_url = root.find(id="ctl00_ContentBody_GeoNav_logButton")["href"]

        js_content = "\n".join(map(lambda i: i.text, root.find_all("script")))
        self._logbook_token = re.findall("userToken\\s*=\\s*'([^']+)'", js_content)[0]
        # find original location if any
        if "oldLatLng\":" in js_content:
            old_lat_long = js_content.split("oldLatLng\":")[1].split(']')[0].split('[')[1]
            self.original_location = Point(old_lat_long)
        else:
            self.original_location = None

        # if there are some trackables
        if len(inventory_widget.find_all("a")) >= 3:
            trackable_page_url = inventory_widget.find(id="ctl00_ContentBody_uxTravelBugList_uxViewAllTrackableItems")
            self._trackable_page_url = trackable_page_url.get("href")[3:]  # has "../" on start
        else:
            self._trackable_page_url = None

        logging.debug("Cache loaded: {}".format(self))
Beispiel #6
0
    def load(self):
        """Load all possible details about the trackable.

        .. note::
           This method is called automatically when you access a property which isn't yet filled in
           (so-called "lazy loading"). You don't have to call it explicitly.

        :raise .LoadError: If trackable loading fails (probably because of not existing cache).
        """
        # pick url based on what info we have right now
        if hasattr(self, "url"):
            url = self.url
        elif hasattr(self, "_tid"):
            url = "track/details.aspx?tracker={}".format(self._tid)
        else:
            raise errors.LoadError("Trackable lacks info for loading")

        # make request
        root = self.geocaching._request(url)

        # parse data
        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")
        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
        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")
        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:
            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 to first
                    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