Exemplo n.º 1
0
    def load(self):
        """ Loads all bank account details """
        # Get the index
        pg = self._get_page('index')

        # Check if the user has a bank account
        if 'I see you don\'t currently have an account with us' in pg.content:
            self._logger.warning('User ' + self._usr.username +
                                 ' does not have a bank account')
            return

        # Load the details
        try:
            rows = self._xpath('rows', pg)
            self.type = str(rows[0].xpath('./td[2]/text()')[0])
            self.balance = self._format_nps(rows[1].xpath('./td[2]/text()')[0])
            self.interest_rate = float(
                rows[2].xpath('./td[2]/b/text()')[0].replace('%', ''))
            self.yearly_interest = self._format_nps(
                rows[3].xpath('./td[2]/text()')[0])

            # Some user's don't have enough for daily interest
            if 'you might want to deposit a few more Neopoints' not in pg.content:
                self.daily_interest = self._format_nps(
                    self._xpath('daily', pg)[0])
                self.interest_collected = True

            if 'You have already collected your interest today' in pg.content:
                self.interest_collected = True
        except Exception:
            self._logger.exception('Failed to parse user bank details',
                                   {'pg': pg})
            raise ParseException('Failed to parse user bank details')
Exemplo n.º 2
0
    def load(self, owner, index, pages=False):
        """ Loads the shop contents

        Arguments:
            | **owner**: The owner of the shop
            | **index**: The index page of the shop
            | **pages**: Whether or not to fetch all shop pages
        """
        # Determine if we're going to load multiple pages
        try:
            if pages:
                # Parse the index
                self._parse_page(index)

                # Keep going until we run out of pages
                i = 1
                while True:
                    lim = i * 80

                    pg = self._get_page('shop', (str(lim), owner))
                    self._parse_page(pg)

                    if 'Next 80 Items' not in pg.content:
                        break

                    i += 1
            else:
                self._parse_page(index)
        except Exception:
            self._logger.exception(
                'Failed to parse user front shop with owner: ' + owner,
                {'pg': pg})
            raise ParseException('Failed to parse user front shop')
Exemplo n.º 3
0
    def load(self):
        """ Loads the inventory for the :class:`User` instance of this class """
        # Load the inventory
        pg = self._get_page('inventory')

        # Loops through all non-NC items
        self.data = []
        try:
            for td in self._xpath('main_inventory', pg):
                id = str(self._xpath('item/id', td)).replace(');', '')
                item = InventoryItem(id, self._usr)

                item.img = str(self._xpath('item/img', td)[0])
                item.desc = str(self._xpath('item/desc', td)[0])
                item.name = str(self._xpath('item/name', td)[0])

                if len(self._xpath('item/rarity', td)) > 0:
                    item.rarity = str(self._xpath('item/rarity', td)[0])
                    item.rarity = item.rarity.replace('(', '').replace(')', '')

                self.data.append(item)
        except Exception:
            self._logger.exception('Unable to parse user\'s inventory',
                                   {'pg': pg})
            raise ParseException('Unable to parse user\'s inventory')
Exemplo n.º 4
0
    def load(self):
        """ Loads the user's sales history """
        # Load the history page
        pg = self._get_page('sales')

        try:
            rows = self._xpath('rows', pg)
            rows.pop(0)
            rows.pop()

            for row in rows:
                trans = Transaction()
                details = self._xpath('details', row)

                trans.date = details[0].text_content()
                trans.item = details[1].text_content()
                trans.buyer = details[2].text_content()
                trans.price = int(
                    self._remove_multi(details[3].text_content(),
                                       [',', ' NP']))

                self.data.append(trans)
        except Exception:
            self._logger.exception('Failed to parse shop history', {'pg': pg})
            raise ParseException('Failed to parse shop history')
Exemplo n.º 5
0
    def till(self):
        pg = self._get_page('till')

        try:
            return int(
                self._remove_multi(self._xpath('till', pg)[0], [' NP', ',']))
        except Exception:
            self._logger.exception('Failed to parse user till', {'pg': pg})
            raise ParseException('Failed to parse user till')
Exemplo n.º 6
0
    def buy(self, price=0):
        """ Attempts to buy the item from the main shop

        This function will take the given price (or use the price of the item
        from the shop if no price is given) and attempt to purchase the item. It
        automatically scans the generated OCR and attempts to find the x,y
        coordinate of the darkest pixel. Returns the result.

        Arguments:
            **price**: Optional price to buy the item at
        Returns:
            Boolean indicating if the purchase was successful
        """
        # Goto the haggle page
        pg = self._get_page('haggle', (self.id, self.stock_id, self.brr))

        # Did we get a price?
        if not price:
            price = self.price

        try:
            # Check to see if the item is still for sale
            if pg.form(action='haggle.phtml') and 'SOLD OUT' not in pg.content:
                form = pg.form(action='haggle.phtml')[0]

                # Download the image
                url = self._base_url + self._xpath('captcha', pg)[0]
                pg = self._usr.get_page(url)

                x, y = self._crack_OCR(io.BytesIO(pg.content))
                form.update(current_offer=str(price), x=str(x), y=str(y))

                # Attempt to buy the item
                pg = form.submit(self._usr)

                if "I accept" in pg.content:
                    return True
                else:
                    return False
            else:
                return False
        except Exception:
            self._logger.exception('Failed to handle haggle page', {'pg': pg})
            raise ParseException('Failed to handle haggle page')
Exemplo n.º 7
0
    def load(self, id, index=None):
        """ Loads the main shop inventory

        Arguments:
            | **id**: The id of the shop to load
            | **index**: Optional index page of the shop to load from
        """
        # Check if we already have the index page
        if not index:
            pg = self._get_page('shop', str(id))
        else:
            pg = index

        # Clear any existing data
        self.data = []

        try:
            for row in self._xpath('rows', pg):
                for td in self._xpath('tds', row):
                    url = td.xpath('./a/@href')[0]
                    onclick = td.xpath('./a/@onclick')[0]

                    id = url.split('obj_info_id=')[1].split('&')[0]
                    stock_id = url.split('stock_id=')[1].split('&')[0]
                    brr = onclick.split('brr=')[1].split('\'')[0]

                    item = MSItem(id, self._usr)
                    item.name = str(td.xpath('./b/text()')[0])
                    item.img = td.xpath('./a/img/@src')
                    item.stock = int(
                        td.xpath('./text()')[0].replace(' in stock', ''))
                    item.price = int(
                        self._remove_multi(
                            td.xpath('./text()')[1], ['Cost: ', ',', ' NP']))
                    item.stock_id = stock_id
                    item.brr = brr

                    self.data.append(item)
        except Exception:
            self._logger.exception(
                'Failed to parse main shop inventory for id: ' + str(id),
                {'pg': pg})
            raise ParseException('Failed to parse main shop inventory')
Exemplo n.º 8
0
    def get_details(self):
        """ Fills in the item details obtained off the item info page

        Most details can be obtained directly off of the user's inventory
        screen, however a few key details like weight and value cannot and this
        function should be called to retrieve these """
        pg = self._get_page('item', self.id)

        if "not in your inventory" in pg.content:
            self._logger.error('No item with id ' + self.id + ' in inventory')
            raise InvalidItemID(self.id)

        try:
            self.name = str(self._xpath('name', pg)[0].replace(' : ', ''))
            self.img = str(self._xpath('image', pg)[0])
            self.desc = str(self._xpath('desc', pg)[0])
            self.type = str(self._xpath('type', pg)[0])
            self.weight = str(self._xpath('weight', pg)[0])
            self.rarity = str(self._xpath('rarity', pg)[0])
            self.value = str(self._xpath('value', pg)[0])
        except Exception:
            self._logger.exception('Failed to parse details for ' + self.name, {'pg': pg})
            raise ParseException('Failed to parse details for ' + self.name)
Exemplo n.º 9
0
    def load(self, index=None):
        """ Loads the shop inventory for the :class:`User` instance of this class

        Arguments:
            **index**: Optional :class:`Page` instance with the user's shop
                index page loaded
        """
        # Load the main index
        if not index:
            pg = self._get_page('index')
        else:
            pg = index

        # Reset items if already loaded
        self.data = []

        # Determine how many pages there are.
        pages = len(self._xpath('pages', pg))

        # The first link is not a page
        self.pages = pages - 1

        # Always have at least one page
        if self.pages <= 0:
            self.pages = 1

        try:
            # Load the items from the first page
            self._parse_page(pg, 1)

            # Grab from the remaining pages. By starting at 2 we ignore the index.
            for i in range(2, pages):
                pg = self._get_page('page', str(i * self._items_per_page))
                self._parse_page(pg, i)
        except Exception:
            self._logger.exception('Failed to parse user shop', {'pg': pg})
            raise ParseException('Failed to parse user shop')
Exemplo n.º 10
0
    def load(self):
        """Fetches the user profile data and populates the attributes

        Query's the user's profile on Neopets using the username of the given
        :class:`User` object when the profile was initialized. This method can
        be called multiple times to update the loaded profile details.

        Raises:
            **ParseException**: An error occured parsing the user's profile
        """

        # Much of the user profile can change without much uniformity. This
        # means certain HTML elements may or may not be in different places.
        # It all depends on how much the user has filled their profile in.
        # Therefore, this class uses a combination of xpath and regular
        # expressions to parse all required information from the profile.

        # Since the pattern for parsing with regular expressions and xpath is
        # fairly similar across all the sections of a user profile, a
        # refactored function was implemented which takes an exception function
        # for dealing with any exceptional fields when running through a
        # profile section.

        # The age of an account is only displayed in the form of an image. This
        # image can represent either the number of weeks, months, or years. The
        # below exception function determines which type of image it is and
        # calculates the proper age.
        def general_exception(self, key, result, html):
            if key == 'age':
                if 'years' in result:
                    self.age = int(result.split("_")[0]) * 12
                elif 'mth' in result:
                    self.age = int(result.split('mth')[0])
                else:
                    self.age = int(result.split('wk')[0]) / 4
                return True
            else:
                return False

        # The number of neocards a user has is display differently depending on
        # if the user has an active deck. The below exception function
        # determines which case exists and parses accordingly.
        def collections2_exception(self, key, result, html):
            if key == 'neocards':
                # If the first match failed it will match an entire sentence.
                # So we just check the results length to see if it failed
                if len(result) > 4:
                    result = self._search('colls2/neocards2', html, True)[0]

                    self.neocards = int(self._remove_extra(result))
                else:
                    self.neocards = int(self._remove_extra(result))
                return True
            elif key == 'neocards2':
                return True
            else:
                return False

        # Get the profile page
        pg = self._get_page('profile', self._usr.username)

        try:
            # Parse the general profile details
            self._set_attributes(pg, 'general', 'general', general_exception)

            # Parse the first set of collections
            self._set_attributes(pg, 'colls1', 'colls1')

            # Parse the second set of collections
            self._set_attributes(pg, 'colls2', 'colls2',
                                 collections2_exception)

            # The user might not have a shop or gallery
            if 'Shop &amp; Gallery' in pg.content:
                self._set_attributes(pg, 'shop_gallery', 'shop_gallery')

            # The neopets are parsed slightly differently
            for td in pg.xpath(self._paths['neopets']):
                html = self._to_html(td)
                pet = Neopet()

                for key in self._regex['neopets'].keys():
                    result = self._search('neopets/' + key, html)
                    if result:
                        setattr(pet, key, self._remove_extra(result[0]))
                self.neopets.append(pet)
        except:
            self._logger.exception(
                'Failed to parse user profile with name: ' +
                self._usr.username, {'pg': pg})
            raise ParseException('Could not parse user profile')
Exemplo n.º 11
0
    def search(self, item, area='shop', criteria='exact', min=0, max=99999):
        """ Searches the shop wizard with the provided details

        Arguments:
            | **item**: The name of the item to search for
            | **area**: Optional area to search for the item
            | **criteria**: Method to search with
            | **min**: The minimum price of the item
            | **max**: The maximum price of the item

        Returns:
            Instance of :class:`WizardItemList` with the search results or None
                if the search returned zero results
        """
        # Load the index
        pg = self._get_page('index')

        # Grab the form and set the values
        form = pg.form(action='market.phtml')[0]
        data = {
            'type': 'process_wizard',
            'feedset': '0',
            'shopwizard': item,
            'table': area,
            'criteria': criteria,
            'min_price': min,
            'max_price': max,
        }

        form.update(data)

        # Submit the form and get the results
        pg = form.submit(self._usr)

        # Check for results
        if 'I did not find anything' in pg.content:
            return []

        # Check for ban
        if 'too many searches' in pg.content:
            raise WizardBanned

        # Parse the results
        try:
            rows = self._xpath('rows', pg)
            rows.pop(0)

            items = []
            for row in rows:
                details = self._xpath('details', row)

                url = details[0].xpath('./a/@href')[0]
                id = url.split('obj_info_id=')[1].split('&')[0]

                item = WizardItem(id, self._usr)
                item.owner = str(details[0].text_content())
                item.name = str(details[1].text_content())
                item.stock = int(details[2].text_content())
                item.price = int(
                    self._remove_multi(details[3].text_content(),
                                       [',', ' NP']))

                items.append(item)
        except Exception:
            self._logger.log('Failed to parse shop wizard results', {'pg': pg})
            raise ParseException('Could not parse shop wizard results')

        return WizardItemList(self._usr, items)
Exemplo n.º 12
0
    def move(self, location):
        """ Moves all items in the current list to the destination specified

        Arguments:
            | **location**: The destination to move the items to

        Returns:
            Boolean inicating if the move was successful

        Example:
            >>> snowballs = usr.inventory.find(name__contains='Snowball')
            >>> snowballs.move(snowballs.SHOP)
            True
        """
        pg = self._get_page('quickstock')
        form = pg.form(action='process_quickstock.phtml')[0]

        # First we need to build a dictionary of positions and id's
        ids = {}
        try:
            for name, inp in form.items():
                if 'id_arr' in name:
                    pos = inp.name.split('[')[1].replace(']', '')
                    ids[pos] = inp.value
        except Exception:
            self._logger.exception('Unable to parse item id\'s')
            raise ParseException('Unable to parse item id\'s')

        # Next we need to remove all positions that are not changing
        remove = []
        for pos in ids.keys():
            found = False
            for item in self.data:
                if item.id == ids[pos]:
                    found = True

            if not found:
                remove.append(pos)

        for pos in remove:
            del ids[pos]

        # Now we need to remove all radio fields that are not changing
        try:
            for name, inp in list(form.items()):
                if 'radio_arr' in name:
                    pos = inp.name.split('[')[1].replace(']', '')
                    if pos not in list(ids.keys()):
                        del form[name]
        except Exception:
            self._logger.exception('Unable to parse radio fields')
            raise ParseException('Unable to parse radio fields')

        # Finally we need to set the destination for remaining radio fields
        if location == self.SDB:
            value = self.SDB
        elif location == self.DONATE:
            value = self.DONATE
        elif location == self.DROP:
            value = self.DROP
        elif location == self.SHOP:
            value = self.SHOP
        elif location == self.GALLERY:
            value = self.GALLERY
        else:
            return False

        for name, inp in list(form.items()):
            if 'radio_arr' in name:
                inp.value = value

        # Now we submit the form and check the result
        pg = form.submit(self._usr)
        if 'red_oops.gif' in pg.content:
            return False
        else:
            return True
Exemplo n.º 13
0
    def __init__(self, usr, owner, item_id='', price=''):
        """ Loads the shops inventory and grabs main item if necessary

        The initialization of this class takes multiple parameters for different
        scenarios. If a shop needs to be loaded (including all shop pages) then
        the `usr` and `owner` arguments should only be supplied. If a specific
        item in the shop is being sought after (like from a shop wizard result)
        then the item id and price should also be supplied. In the latter case
        only the first page of the user shop is loaded and if the item is found
        it will be appended to the very beginning of the inventory list.

        Arguments:
            | **usr**: The :class:`User` instance to use
            | **owner**: The username of the owner of the shop
            | **item_id**: The item id to search for
            | **price**: The price of the item to search for
        """
        super().__init__(usr)

        # Load the appropriate page
        if item_id and price:
            pg = self._get_page('shop_item', (owner, item_id, str(price)))
        else:
            pg = self._get_page('shop', owner)

        # Get the details
        try:
            self.name = str(self._xpath('name', pg)[0])
            self.keeper_img = self._xpath('keeper_img', pg)[0]
            self.keeper_name = str(
                self._xpath('keeper_text', pg)[0].split(' says')[0])
            self.keeper_message = str(
                self._xpath('keeper_text',
                            pg)[0].split('says ')[1].replace('\'', ''))
        except Exception:
            self._logger.exception('Failed to parse user front shop details')
            raise ParseException('Failed to parse user front shop details')

        # Load the inventory
        self.inventory = USFrontInventory(self._usr)

        if item_id and price:
            self.inventory.load(owner, pg)
        else:
            self.inventory.load(owner, pg, True)

        # If searching for a specific item, find it
        if item_id and price:
            try:
                td = self._xpath('main_item', pg)[0]
                item = USFrontItem('', self._usr)

                item.url = td.xpath('./a/@href')[0]
                item.name = str(td.xpath('./b/text()')[0])
                item.stock = int(
                    td.xpath('./text()[3]')[0].replace(' in stock', ''))
                item.price = int(
                    self._remove_multi(
                        td.xpath('./text()[4]')[0], [',', ' NP', 'Cost : ']))

                # Add it to the beginning of the inventory
                self.inventory.data = [item] + self.inventory.data
            except Exception:
                # This most likely means the item has already been bought
                pass