Example #1
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(
        "competitive" if mode == "competitive" else "quickplay"))
    if not _root:
        return

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']".
            format(requested_hero_div_id))

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]
        _average_stats = {}

        _t_d = {}
        hero_specific_box = stat_groups[0]
        trs = hero_specific_box.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text

            # Put averages into average_stats
            if "average" in name:
                into = _average_stats
            else:
                into = _t_d
            nvl = util.try_extract(value)
            into[name] = nvl

        n_dict["hero_stats"] = _t_d

        _t_d = {}
        for subbox in stat_groups[1:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(
                    subval[0].text), subval[1].text
                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["general_stats"] = _t_d
        n_dict["average_stats"] = _average_stats

        built_dict[hero_name] = n_dict

    return built_dict
Example #2
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(
        ".//div[@id='{}']".format("competitive" if mode == "competitive" else "quickplay")
    )
    if not _root:
        return

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']".format(requested_hero_div_id)
        )

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]

        _t_d = {}
        hero_specific_box = stat_groups[0]
        trs = hero_specific_box.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            if 'average' in name.lower():
                # No averages, ty
                continue
            nvl = util.try_extract(value)
            _t_d[name] = nvl

        n_dict["hero_stats"] = _t_d

        _t_d = {}
        for subbox in stat_groups[1:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(subval[0].text), subval[1].text
                if 'average' in name.lower():
                    # No averages, ty
                    continue
                nvl = util.try_extract(value)
                _t_d[name] = nvl

        n_dict["general_stats"] = _t_d

        built_dict[hero_name] = n_dict

    return built_dict
Example #3
0
def bl_parse_all_heroes(parsed, mode="quickplay"):
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(mode))
    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(
            ".//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    if mode == "competitive":
        _root = parsed.findall(".//div[@data-mode='competitive']")[0]
    else:
        _root = parsed

    _hero_info = _root.findall(".//div[@data-group-id='comparisons']")[0]
    hero_info = _hero_info.findall(".//div[@class='bar-text']")

    # Loop over each one, extracting the name and hours counted.
    for child in hero_info:
        name, played = child.getchildren()
        name, played = util.sanitize_string(name.text), played.text.lower()

        if played == "--":
            time = 0
        else:
            time = util.try_extract(played)
        built_dict[name] = time

    return built_dict
Example #4
0
def bl_parse_all_heroes(parsed, mode="quickplay"):
    built_dict = {}

    if mode == "competitive":
        _hero_info = parsed.findall(
            ".//div[@id='competitive-play']/section/div/div[@data-group-id='comparisons']"
        )[0]
    elif mode == "quickplay":
        _hero_info = parsed.findall(".//div[@data-group-id='comparisons']")[0]
    else:
        _hero_info = parsed.findall(".//div[@data-group-id='comparisons']")[0]

    hero_info = _hero_info.findall(".//div[@class='bar-text']")

    # Loop over each one, extracting the name and hours counted.
    for child in hero_info:
        name, played = child.getchildren()
        name, played = util.sanitize_string(name.text), played.text.lower()

        if played == "--":
            time = 0
        else:
            time = util.try_extract(played)
        built_dict[name] = time

    return built_dict
Example #5
0
def bl_parse_achievement_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(".//section[@id='achievements-section']")
    if not _root:
        return
    _root = _root[0]

    _category_selects = _root.xpath(
        ".//select[@data-group-id='achievements']")[0].xpath(".//option")

    for _category_select in _category_selects:
        category_name = _category_select.text
        category_id = _category_select.get("value")

        _achievement_boxes = _root.xpath(
            ".//div[@data-group-id='achievements' and @data-category-id='{0}']"
            "/ul/div/div[@data-tooltip]".format(category_id))
        n_dict = {}

        for _achievement_box in _achievement_boxes:
            achievement_name = _achievement_box.xpath("./div/div")[0].text
            if achievement_name == "?":
                # Sombra ARG clue, not a real achievement
                continue

            n_dict[util.sanitize_string(
                achievement_name)] = "m-disabled" not in _achievement_box.get(
                    "class")

        built_dict[category_name.lower()] = n_dict

    return built_dict
Example #6
0
def bl_parse_all_heroes(parsed, mode="quickplay"):
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(mode))
    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(".//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip() == "We don't have any data for this account in this mode yet.":
            return None

    if mode == "competitive":
        _root = parsed.findall(".//div[@data-mode='competitive']")[0]
    else:
        _root = parsed

    _hero_info = _root.findall(".//div[@data-group-id='comparisons']")[0]
    hero_info = _hero_info.findall(".//div[@class='bar-text']")

    # Loop over each one, extracting the name and hours counted.
    for child in hero_info:
        name, played = child.getchildren()
        name, played = util.sanitize_string(name.text), played.text.lower()

        if played == "--":
            time = 0
        else:
            time = util.try_extract(played)
        built_dict[name] = time

    return built_dict
Example #7
0
def bl_parse_achievement_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(
        ".//section[@id='achievements-section']"
    )
    if not _root:
        return
    _root = _root[0]

    _category_selects = _root.xpath(".//select[@data-group-id='achievements']")[0].xpath(".//option")

    for _category_select in _category_selects:
        category_name = _category_select.text
        category_id = _category_select.get("value")

        _achievement_boxes = _root.xpath(
            ".//div[@data-group-id='achievements' and @data-category-id='{0}']/ul/div/div[@data-tooltip]".format(
                category_id))
        n_dict = {}

        for _achievement_box in _achievement_boxes:
            achievement_name = _achievement_box.xpath("./div/div")[0].text
            if achievement_name == '?':
                # Sombra ARG clue, not a real achievement
                continue

            n_dict[util.sanitize_string(achievement_name)] = "m-disabled" not in _achievement_box.get("class")

        built_dict[category_name.lower()] = n_dict

    return built_dict
Example #8
0
def bl_parse_all_heroes(parsed, mode="quickplay"):
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(mode))

    # maybe this isn't needed anymore??
    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(".//ul/h6[@class='u-align-center']")[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    if mode == "competitive":
        # Fix for #287. The competitive play section doesn't exist if the user doesn't have any
        # competitive playtime.
        try:
            _root = parsed.findall(".//div[@data-mode='competitive']")[0]
        except IndexError:
            return None
    else:
        _root = parsed

    _hero_info = _root.xpath(".//div[@data-group-id='comparisons' and "
                             "@data-category-id='0x0860000000000021']")[0]
    hero_info = _hero_info.findall(".//div[@class='ProgressBar-textWrapper']")

    # Loop over each one, extracting the name and hours counted.
    percent_per_second = None
    for child in reversed(hero_info):
        name, played = child.getchildren()
        if not name.text:
            continue

        name, played = util.sanitize_string(name.text), played.text.lower()

        time = 0
        if played != "--":
            time = util.try_extract(played)

        # More accurate playtime calculation
        # Requires reversing hero_info
        category_item = child.getparent().getparent()
        percent = float(
            category_item.attrib["data-overwatch-progress-percent"])
        if percent_per_second is None and 1 > time > 0:
            seconds = 3600 * time
            percent_per_second = percent / seconds

        built_dict[name] = time
        if percent_per_second is not None:
            built_dict[name] = (percent / percent_per_second) / float(3600)

    return built_dict
Example #9
0
def bl_parse_all_heroes(parsed, mode="quickplay"):
    built_dict = {}

    if mode == "competitive":
        _hero_info = parsed.findall(".//div[@data-mode='competitive']//div[@data-group-id='comparisons']")[0]
    elif mode == "quickplay":
        _hero_info = parsed.findall(".//div[@data-group-id='comparisons']")[0]
    else:
        _hero_info = parsed.findall(".//div[@data-group-id='comparisons']")[0]

    hero_info = _hero_info.findall(".//div[@class='bar-text']")

    # Loop over each one, extracting the name and hours counted.
    for child in hero_info:
        name, played = child.getchildren()
        name, played = util.sanitize_string(name.text), played.text.lower()

        if played == "--":
            time = 0
        else:
            time = util.try_extract(played)
        built_dict[name] = time

    return built_dict
Example #10
0
def bl_parse_stats(parsed, mode="quickplay"):
    # Just a quick FYI
    # If future me or future anyone else is looking at this, I do not how this code works.
    # I'm really really hoping it doesn't break.
    # Good luck!

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = parsed.xpath(
            ".//div[@id='{}']//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Shortcut location for player level etc
    mast_head = parsed.xpath(".//div[@class='masthead-player']")[0]

    # Get the prestige.
    prestige = mast_head.xpath(".//div[@class='player-level']")[0]
    # Extract the background-image from the styles.
    try:
        bg_image = [x for x in prestige.values() if 'background-image' in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE.items():
            if key in bg_image:
                prestige_num = val
                built_dict["overall_stats"]["rank_image"] = bg_image.split(
                    "(")[1][:-1]
                break
        else:
            # Unknown.
            prestige_num = None
        built_dict["overall_stats"]["prestige"] = prestige_num

    # Parse out the HTML.
    level = int(prestige.findall(".//div")[0].text)
    built_dict["overall_stats"]["level"] = level

    try:
        tier = mast_head.xpath(".//div[@class='competitive-rank']/img")[0]
        img_src = [x for x in tier.values() if 'rank-icons' in x][0]
    except IndexError:
        built_dict['overall_stats']['tier'] = None
    else:
        for key, val in tier_data_img_src.items():
            if key in img_src:
                tier_str = val
                break
        else:
            tier_str = None
        built_dict["overall_stats"]["tier"] = tier_str

    hasrank = mast_head.findall(".//div[@class='competitive-rank']/div")
    if hasrank:
        comprank = int(hasrank[0].text)
    else:
        comprank = None
    built_dict["overall_stats"]["comprank"] = comprank

    # Fetch Avatar
    built_dict["overall_stats"]["avatar"] = mast_head.find(
        ".//img[@class='player-portrait']").attrib['src']

    if mode == "competitive":
        hascompstats = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )
        if len(hascompstats) != 2:
            return None
        stat_groups = hascompstats[1]
    elif mode == "quickplay":
        try:
            stat_groups = parsed.xpath(
                ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # User has no stats...
            return None
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[6]
    except IndexError:
        try:
            game_box = stat_groups[5]
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(
            game_box.xpath(".//text()[. = 'Games Won']/../..")[0]
            [1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
    else:
        games = int(g[0][1].text.replace(",", ""))

    if mode == "competitive":
        try:
            misc_box = stat_groups[7]
            losses = int(
                misc_box.xpath(".//text()[. = 'Games Lost']/../..")[0]
                [1].text.replace(",", ""))
            ties = int(
                misc_box.xpath(".//text()[. = 'Games Tied']/../..")[0]
                [1].text.replace(",", ""))
        except IndexError:
            # Sometimes the losses and ties don't exist.
            # I'm not 100% as to what causes this, but it might be because there are no ties.
            # In this case, just set ties to 0, and calculate losses manually.
            ties = 0
            # Quickplay shit.
            # Goddamnit blizzard.
            if games is None:
                losses = 0
                games = 0
                wins = 0
            else:
                # Competitive stats do have these values (for now...)
                losses = games - wins

        if games == 0 or games == ties:
            wr = 0
        else:
            wr = round((wins / (games - ties)) * 100, 2)

        built_dict["overall_stats"]["ties"] = ties
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = wr

    # Update the dictionary.
    built_dict["overall_stats"]["wins"] = wins

    # Build a dict using the stats.
    _t_d = {}
    _a_d = {}
    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)
            if 'average' in name.lower():
                _a_d[name.replace("_average", "_avg")] = nvl
            else:
                _t_d[name] = nvl

    # Manually add the KPD.
    try:
        _t_d["kpd"] = round(_t_d["eliminations"] / _t_d["deaths"], 2)
    except KeyError:
        # They don't have any eliminations/deaths.
        # Set the KPD to 0.0.
        # See: #106
        _t_d["kpd"] = 0

    built_dict["game_stats"] = _t_d
    built_dict["average_stats"] = _a_d
    built_dict["competitive"] = mode == "competitive"

    if not "games" in built_dict["overall_stats"]:
        # manually calculate it
        dmg_done = built_dict["game_stats"]["damage_done"]
        avg_dmgd = built_dict["average_stats"]["damage_done_avg"]

        # IT RETURNS
        games = int(dmg_done // avg_dmgd)

        losses = games - built_dict["overall_stats"]["wins"]
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = round(
            (built_dict["overall_stats"]["wins"] / games) * 100, 2)

    return built_dict
Example #11
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(
        "competitive" if mode == "competitive" else "quickplay"))
    if not _root:
        return None

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(
            ".//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']".
            format(requested_hero_div_id))

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]
        _average_stats = {}

        _t_d = {}

        # offset for subboxes
        # if there IS a hero-specific box, we need to scan all boxes from offset to end
        # because the hero-specific box is first.
        # if there is NOT, we scan all boxes later.
        # this is determined by the xpath to find the Hero Specific page.
        subbox_offset = 0

        # .find on the assumption hero box is the *first* item
        hbtitle = stat_groups.find(".//span[@class='stat-title']").text
        if hbtitle == "Hero Specific":
            subbox_offset = 1
            hero_specific_box = stat_groups[0]
            trs = hero_specific_box.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(
                    subval[0].text), subval[1].text

                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["hero_stats"] = _t_d

        _t_d = {}
        for subbox in stat_groups[subbox_offset:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(
                    subval[0].text), subval[1].text
                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["general_stats"] = _t_d
        n_dict["average_stats"] = _average_stats

        built_dict[hero_name] = n_dict

    return built_dict
Example #12
0
def bl_parse_stats(parsed, mode="quickplay"):
    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Get the prestige.
    prestige = parsed.xpath(".//div[@class='player-level']")[0]
    # Extract the background-image from the styles.
    try:
        bg_image = [x for x in prestige.values() if 'background-image' in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE.items():
            if key in bg_image:
                prestige_num = val
                break
        else:
            # Unknown.
            prestige_num = None
        built_dict["overall_stats"]["prestige"] = prestige_num

    # Parse out the HTML.
    level = int(parsed.findall(".//div[@class='player-level']/div")[0].text)
    built_dict["overall_stats"]["level"] = level

    hasrank = parsed.findall(".//div[@class='competitive-rank']/div")
    if hasrank:
        comprank = int(hasrank[0].text)
    else:
        comprank = None
    built_dict["overall_stats"]["comprank"] = comprank

    # Fetch Avatar
    built_dict["overall_stats"]["avatar"] = parsed.find(
        ".//img[@class='player-portrait']").attrib['src']

    if mode == "competitive":
        hascompstats = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )
        if len(hascompstats) != 2:
            return {}
        stat_groups = hascompstats[1]
    elif mode == "quickplay":
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[6]
    except IndexError:
        try:
            game_box = stat_groups[5]
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(
            game_box.xpath(".//text()[. = 'Games Won']/../..")[0]
            [1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
        wr = 0
    else:
        games = int(g[0][1].text.replace(",", ""))
        losses = games - wins
        wr = floor((wins / games) * 100)

    # Update the dictionary.
    built_dict["overall_stats"]["games"] = games
    built_dict["overall_stats"]["losses"] = losses
    built_dict["overall_stats"]["wins"] = wins
    built_dict["overall_stats"]["win_rate"] = wr

    # Build a dict using the stats.
    _t_d = {}
    _a_d = {}
    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)
            if 'average' in name.lower():
                _a_d[name.replace("_average", "_avg")] = nvl
            else:
                _t_d[name] = nvl

    # Manually add the KPD.
    _t_d["kpd"] = round(_t_d["eliminations"] / _t_d["deaths"], 2)

    built_dict["game_stats"] = _t_d
    built_dict["average_stats"] = _a_d
    built_dict["competitive"] = mode == "competitive"

    return built_dict
Example #13
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(
        ".//div[@id='{}']".format("competitive" if mode == "competitive" else "quickplay")
    )
    if not _root:
        return None

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(".//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip() == "We don't have any data for this account in this mode yet.":
            return None

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']"
                .format(requested_hero_div_id)
        )

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]
        _average_stats = {}

        _t_d = {}

        # offset for subboxes
        # if there IS a hero-specific box, we need to scan all boxes from offset to end
        # because the hero-specific box is first.
        # if there is NOT, we scan all boxes later.
        # this is determined by the xpath to find the Hero Specific page.
        subbox_offset = 0

        # .find on the assumption hero box is the *first* item
        hbtitle = stat_groups.find(".//span[@class='stat-title']").text
        if hbtitle == "Hero Specific":
            subbox_offset = 1
            hero_specific_box = stat_groups[0]
            trs = hero_specific_box.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(subval[0].text), subval[1].text

                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["hero_stats"] = _t_d

        _t_d = {}
        for subbox in stat_groups[subbox_offset:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(subval[0].text), subval[1].text
                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["general_stats"] = _t_d
        n_dict["average_stats"] = _average_stats

        built_dict[hero_name] = n_dict

    return built_dict
Example #14
0
def bl_parse_stats(parsed, mode="quickplay", status=None):
    # Just a quick FYI
    # If future me or future anyone else is looking at this, I do not how this code works.
    # I'm really really hoping it doesn't break.
    # Good luck!

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = parsed.xpath(
            ".//div[@id='{}']//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Shortcut location for player level etc
    if not status or status.lower() != "public profile":
        hasrank = parsed.xpath(
            '//*[@id="overview-section"]/div/div/div/div/div[2]/div/div[3]/div'
        )
        if hasrank:
            comprank = int(hasrank[0].text)
        else:
            comprank = None
        built_dict["overall_stats"]["comprank"] = comprank
        return built_dict

    mast_head = parsed.xpath(".//div[@class='masthead-player']")[0]

    # Get the prestige.
    prestige = mast_head.xpath(".//div[@class='player-level']")[0]
    # Extract the background-image from the styles.
    try:
        bg_image = [x for x in prestige.values() if 'background-image' in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE.items():
            if key in bg_image:
                prestige_num = val
                built_dict["overall_stats"]["rank_image"] = bg_image.split(
                    "(")[1][:-1]
                break
        else:
            # Unknown.
            prestige_num = None
        built_dict["overall_stats"]["prestige"] = prestige_num

    # Parse out the HTML.
    level = int(prestige.findall(".//div")[0].text)
    built_dict["overall_stats"]["level"] = level

    # Get and parse out endorsement level.
    endorsement = mast_head.xpath(".//div[@class='endorsement-level']")[0]
    built_dict["overall_stats"]["endorsement_level"] = int(
        endorsement.findall(".//div[@class='u-center']")[0].text)

    # Get endorsement circle.
    endorsement_icon_inner = mast_head.xpath(
        ".//div[@class='endorsement-level']/div[@class='EndorsementIcon']/div[@class='EndorsementIcon-inner']"
    )[0]

    # Get individual endorsement segments.
    try:
        endorsement_shotcaller_image = endorsement_icon_inner.findall(
            ".//svg[@class='EndorsementIcon-border EndorsementIcon-border--shotcaller']"
        )[0]
        endorsement_shotcaller_level = endorsement_shotcaller_image.get(
            'data-value')
    except:
        endorsement_shotcaller_level = 0

    try:
        endorsement_teammate_image = endorsement_icon_inner.findall(
            ".//svg[@class='EndorsementIcon-border EndorsementIcon-border--teammate']"
        )[0]
        endorsement_teammate_level = endorsement_teammate_image.get(
            'data-value')
    except:
        endorsement_teammate_level = 0

    try:
        endorsement_sportsmanship_image = endorsement_icon_inner.findall(
            ".//svg[@class='EndorsementIcon-border EndorsementIcon-border--sportsmanship']"
        )[0]
        endorsement_sportsmanship_level = endorsement_sportsmanship_image.get(
            'data-value')
    except:
        endorsement_sportsmanship_level = 0

    # Parse out endorsement segements.
    built_dict["overall_stats"][
        "endorsement_shotcaller"] = endorsement_shotcaller_level
    built_dict["overall_stats"][
        "endorsement_teammate"] = endorsement_teammate_level
    built_dict["overall_stats"][
        "endorsement_sportsmanship"] = endorsement_sportsmanship_level

    # Get comp rank.
    try:
        tier = mast_head.xpath(".//div[@class='competitive-rank']/img")[0]
        img_src = [x for x in tier.values() if 'rank-icons' in x][0]
        built_dict["overall_stats"]["tier_image"] = img_src
    except IndexError:
        built_dict['overall_stats']['tier'] = None
    else:
        for key, val in tier_data_img_src.items():
            if key in img_src:
                tier_str = val
                break
        else:
            tier_str = None
        built_dict["overall_stats"]["tier"] = tier_str

    hasrank = mast_head.findall(".//div[@class='competitive-rank']/div")
    if hasrank:
        comprank = int(hasrank[0].text)
    else:
        comprank = None
    built_dict["overall_stats"]["comprank"] = comprank

    # Fetch Avatar
    built_dict["overall_stats"]["avatar"] = mast_head.find(
        ".//img[@class='player-portrait']").attrib['src']

    if mode == "competitive":
        # the competitive overview is under a div with id='competitive'
        # and the right category
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='competitive']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # No stats
            return None
    elif mode == "quickplay":
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='quickplay']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # User has no stats...
            return None
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[5]
    except IndexError:
        try:
            game_box = stat_groups[4]
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(
            game_box.xpath(".//text()[. = 'Games Won']/../..")[0]
            [1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
    else:
        games = int(g[0][1].text.replace(",", ""))

    if mode == "competitive":
        try:
            misc_box = stat_groups[7]
            losses = int(
                misc_box.xpath(".//text()[. = 'Games Lost']/../..")[0]
                [1].text.replace(",", ""))
            ties = int(
                misc_box.xpath(".//text()[. = 'Games Tied']/../..")[0]
                [1].text.replace(",", ""))
        except IndexError:
            # Sometimes the losses and ties don't exist.
            # I'm not 100% as to what causes this, but it might be because there are no ties.
            # In this case, just set ties to 0, and calculate losses manually.
            ties = 0
            # Quickplay shit.
            # Goddamnit blizzard.
            if games is None:
                losses = 0
                games = 0
                wins = 0
            else:
                # Competitive stats do have these values (for now...)
                losses = games - wins

        if games == 0 or games == ties:
            wr = 0
        else:
            wr = round((wins / (games - ties)) * 100, 2)

        built_dict["overall_stats"]["ties"] = ties
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = wr

    # Update the dictionary.
    built_dict["overall_stats"]["wins"] = wins

    # Build a dict using the stats.
    game_stats = {}
    average_stats = {}
    rolling_average_stats = {}

    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)

            if 'average' in name.lower():
                average_stats[name.replace("_average", "_avg")] = nvl
            elif '_avg_per_10_min' in name.lower():
                # 2017-08-03 - calculate rolling averages.
                name = name.lower().replace("_avg_per_10_min", "")
                rolling_average_stats[name] = nvl
            else:
                game_stats[name] = nvl

    # Manually add the KPD.
    try:
        game_stats["kpd"] = round(
            game_stats["eliminations"] / game_stats["deaths"], 2)
    except KeyError:
        # They don't have any eliminations/deaths.
        # Set the KPD to 0.0.
        # See: #106
        game_stats["kpd"] = 0

    built_dict["game_stats"] = game_stats
    built_dict["average_stats"] = average_stats
    built_dict["rolling_average_stats"] = rolling_average_stats
    built_dict["competitive"] = mode == "competitive"

    if "games" not in built_dict["overall_stats"]:
        # manually calculate it
        # 2017-07-04 - changed to use eliminations
        # since damage done gave a bit of a stupid amount
        # 2017-07-11 - changed to cycle some averages
        average_keys = ("eliminations", "healing_done", "final_blows",
                        "objective_kills")
        for key in average_keys:
            try:
                total = built_dict["game_stats"][key]
                avg = built_dict["average_stats"][key + "_avg"]
            except KeyError:
                continue
            else:
                got = True
                break
        else:
            got = False

        if got:
            games = int(total // avg)

            losses = games - built_dict["overall_stats"]["wins"]
            built_dict["overall_stats"]["games"] = games
            built_dict["overall_stats"]["losses"] = losses
            built_dict["overall_stats"]["win_rate"] = round(
                (built_dict["overall_stats"]["wins"] / games) * 100, 2)
        else:
            # lol make them up
            built_dict["overall_stats"]["games"] = 0
            built_dict["overall_stats"]["losses"] = 0
            built_dict["overall_stats"]["win_rate"] = 0

    return built_dict
Example #15
0
def bl_parse_stats(parsed, mode="quickplay"):
    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Get the prestige.
    prestige = parsed.xpath(".//div[@class='player-level']")[0]
    # Extract the background-image from the styles.
    try:
        bg_image = [x for x in prestige.values() if 'background-image' in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE.items():
            if key in bg_image:
                prestige_num = val
                break
        else:
            # Unknown.
            prestige_num = None
        built_dict["overall_stats"]["prestige"] = prestige_num

    # Parse out the HTML.
    level = int(parsed.findall(".//div[@class='player-level']/div")[0].text)
    built_dict["overall_stats"]["level"] = level

    hasrank = parsed.findall(".//div[@class='competitive-rank']/div")
    if hasrank:
        comprank = int(hasrank[0].text)
    else:
        comprank = None
    built_dict["overall_stats"]["comprank"] = comprank

    # Fetch Avatar
    built_dict["overall_stats"]["avatar"] = parsed.find(".//img[@class='player-portrait']").attrib['src']

    if mode == "competitive":
        hascompstats = parsed.xpath(".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']")
        if len(hascompstats) != 2:
            return {}
        stat_groups = hascompstats[1]
    elif mode == "quickplay":
        try:
            stat_groups = parsed.xpath(".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']")[0]
        except IndexError:
            # User has no stats...
            return {}
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']")[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[6]
    except IndexError:
        try:
            game_box = stat_groups[5]
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(game_box.xpath(".//text()[. = 'Games Won']/../..")[0][1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
    else:
        games = int(g[0][1].text.replace(",", ""))

    if mode == "competitive":
        try:
            misc_box = stat_groups[7]
            losses = int(misc_box.xpath(".//text()[. = 'Games Lost']/../..")[0][1].text.replace(",", ""))
            ties = int(misc_box.xpath(".//text()[. = 'Games Tied']/../..")[0][1].text.replace(",", ""))
        except IndexError:
            # Sometimes the losses and ties don't exist.
            # I'm not 100% as to what causes this, but it might be because there are no ties.
            # In this case, just set ties to 0, and calculate losses manually.
            ties = 0
            # Quickplay shit.
            # Goddamnit blizzard.
            if games is None:
                losses = 0
                games = 0
                wins = 0
            else:
                # Competitive stats do have these values (for now...)
                losses = games - wins

        if games == 0 or games == ties:
            wr = 0
        else:
            wr = floor((wins / (games - ties)) * 100)

        built_dict["overall_stats"]["ties"] = ties
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = wr

    # Update the dictionary.
    built_dict["overall_stats"]["wins"] = wins

    # Build a dict using the stats.
    _t_d = {}
    _a_d = {}
    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)
            if 'average' in name.lower():
                _a_d[name.replace("_average", "_avg")] = nvl
            else:
                _t_d[name] = nvl

    # Manually add the KPD.
    try:
        _t_d["kpd"] = round(_t_d["eliminations"] / _t_d["deaths"], 2)
    except KeyError:
        # They don't have any eliminations/deaths.
        # Set the KPD to 0.0.
        # See: #106
        _t_d["kpd"] = 0

    built_dict["game_stats"] = _t_d
    built_dict["average_stats"] = _a_d
    built_dict["competitive"] = mode == "competitive"

    return built_dict
Example #16
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(
        "competitive" if mode == "competitive" else "quickplay"))
    if not _root:
        return None

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(".//ul/h6[@class='u-align-center']")[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']".
            format(requested_hero_div_id))

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]
        _average_stats = {}
        _t_d = {}
        _rolling_avgs = {}
        # offset for subboxes
        # if there IS a hero-specific box, we need to scan all boxes from offset to end
        # because the hero-specific box is first.
        # if there is NOT, we scan all boxes later.
        # this is determined by the xpath to find the Hero Specific page.
        subbox_offset = 0

        # .find on the assumption hero box is the *first* item
        # hbtitle = None
        # try:
        # hbtitle = stat_groups.find(".//span[@class='stat-title']").text
        # except AttributeError:
        # try:
        # hbtitle = stat_groups.find(".//h5[@class='stat-title']").text
        # except AttributeError:
        # Unable to parse stat boxes. This is likely due to 0 playtime on a hero, so there are no stats
        # pass

        for idx, sg in enumerate(stat_groups):
            stat_group_hero_specific = stat_groups[idx].find(
                './/*[@class="stat-title"]').text

            if stat_group_hero_specific.lower() == "hero specific":
                try:
                    hero_specific_box = stat_groups[idx]
                    trs = hero_specific_box.findall(".//tbody/tr")

                    # Update the dict with [0]: [1]
                    for subval in trs:
                        name, value = util.sanitize_string(
                            subval[0].text), subval[1].text

                        # Put averages into average_stats
                        if "_avg_per_10_min" in name.lower():
                            into = _rolling_avgs
                            name = name.lower().replace("_avg_per_10_min", "")
                        else:
                            into = _t_d
                        nvl = util.try_extract(value)
                        into[name] = nvl
                    break
                except IndexError:
                    pass

        n_dict["hero_stats"] = _t_d
        _t_d = {}

        for subbox in stat_groups[subbox_offset:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(
                    subval[0].text), subval[1].text

                if "_avg_per_10_min" in name:
                    into = _rolling_avgs
                    name = name.replace("_avg_per_10_min", "")
                elif name in n_dict["hero_stats"]:
                    into = None
                else:
                    into = _t_d

                nvl = util.try_extract(value)

                # Correct Blizzard Singular Plural Bug
                if "_plural_" in name:
                    name = util.correct_plural_name(name, nvl)

                if into != None:
                    into[name] = nvl

        n_dict["general_stats"] = _t_d
        n_dict["average_stats"] = _average_stats
        n_dict["rolling_average_stats"] = _rolling_avgs

        built_dict[hero_name] = n_dict

    return built_dict
Example #17
0
def bl_parse_stats(parsed, mode="quickplay"):
    # Just a quick FYI
    # If future me or future anyone else is looking at this, I do not how this code works.
    # I'm really really hoping it doesn't break.
    # Good luck!

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = parsed.xpath(".//div[@id='{}']//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip() == "We don't have any data for this account in this mode yet.":
            return None

    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Shortcut location for player level etc
    mast_head = parsed.xpath(".//div[@class='masthead-player']")[0]

    # Get the prestige.
    prestige = mast_head.xpath(".//div[@class='player-level']")[0]
    # Extract the background-image from the styles.
    try:
        bg_image = [x for x in prestige.values() if 'background-image' in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE.items():
            if key in bg_image:
                prestige_num = val
                built_dict["overall_stats"]["rank_image"] = bg_image.split("(")[1][:-1]
                break
        else:
            # Unknown.
            prestige_num = None
        built_dict["overall_stats"]["prestige"] = prestige_num

    # Parse out the HTML.
    level = int(prestige.findall(".//div")[0].text)
    built_dict["overall_stats"]["level"] = level

    try:
        tier = mast_head.xpath(".//div[@class='competitive-rank']/img")[0]
        img_src = [x for x in tier.values() if 'rank-icons' in x][0]
    except IndexError:
        built_dict['overall_stats']['tier'] = None
    else:
        for key, val in tier_data_img_src.items():
            if key in img_src:
                tier_str = val
                break
        else:
            tier_str = None
        built_dict["overall_stats"]["tier"] = tier_str

    hasrank = mast_head.findall(".//div[@class='competitive-rank']/div")
    if hasrank:
        comprank = int(hasrank[0].text)
    else:
        comprank = None
    built_dict["overall_stats"]["comprank"] = comprank

    # Fetch Avatar
    built_dict["overall_stats"]["avatar"] = mast_head.find(
        ".//img[@class='player-portrait']"
    ).attrib['src']

    if mode == "competitive":
        # the competitive overview is under a div with id='competitive'
        # and the right category
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='competitive']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # No stats
            return None
    elif mode == "quickplay":
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='quickplay']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # User has no stats...
            return None
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[6]
    except IndexError:
        try:
            game_box = stat_groups[5]
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(game_box.xpath(".//text()[. = 'Games Won']/../..")[0][1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
    else:
        games = int(g[0][1].text.replace(",", ""))

    if mode == "competitive":
        try:
            misc_box = stat_groups[7]
            losses = int(misc_box.xpath(".//text()[. = 'Games Lost']/../..")[0][1].text
                         .replace(",", ""))
            ties = int(misc_box.xpath(".//text()[. = 'Games Tied']/../..")[0][1].text
                       .replace(",", ""))
        except IndexError:
            # Sometimes the losses and ties don't exist.
            # I'm not 100% as to what causes this, but it might be because there are no ties.
            # In this case, just set ties to 0, and calculate losses manually.
            ties = 0
            # Quickplay shit.
            # Goddamnit blizzard.
            if games is None:
                losses = 0
                games = 0
                wins = 0
            else:
                # Competitive stats do have these values (for now...)
                losses = games - wins

        if games == 0 or games == ties:
            wr = 0
        else:
            wr = round((wins / (games - ties)) * 100, 2)

        built_dict["overall_stats"]["ties"] = ties
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = wr

    # Update the dictionary.
    built_dict["overall_stats"]["wins"] = wins

    # Build a dict using the stats.
    _t_d = {}
    _a_d = {}
    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)
            if 'average' in name.lower():
                _a_d[name.replace("_average", "_avg")] = nvl
            else:
                _t_d[name] = nvl

    # Manually add the KPD.
    try:
        _t_d["kpd"] = round(_t_d["eliminations"] / _t_d["deaths"], 2)
    except KeyError:
        # They don't have any eliminations/deaths.
        # Set the KPD to 0.0.
        # See: #106
        _t_d["kpd"] = 0

    built_dict["game_stats"] = _t_d
    built_dict["average_stats"] = _a_d
    built_dict["competitive"] = mode == "competitive"

    if "games" not in built_dict["overall_stats"]:
        # manually calculate it
        # 2017-07-04 - changed to use eliminations
        # since damage done gave a bit of a stupid amount
        # 2017-07-11 - changed to cycle some averages
        average_keys = ("eliminations", "healing_done", "final_blows", "objective_kills")
        for key in average_keys:
            try:
                total = built_dict["game_stats"][key]
                avg = built_dict["average_stats"][key + "_avg"]
            except KeyError:
                continue
            else:
                got = True
                break
        else:
            got = False

        if got:
            games = int(total // avg)

            losses = games - built_dict["overall_stats"]["wins"]
            built_dict["overall_stats"]["games"] = games
            built_dict["overall_stats"]["losses"] = losses
            built_dict["overall_stats"]["win_rate"] = round(
                (built_dict["overall_stats"]["wins"] / games) * 100, 2
            )
        else:
            # lol make them up
            built_dict["overall_stats"]["games"] = 0
            built_dict["overall_stats"]["losses"] = 0
            built_dict["overall_stats"]["win_rate"] = 0

    return built_dict
Example #18
0
def bl_parse_stats(parsed, mode="quickplay", status=None):
    # Just a quick FYI
    # If future me or future anyone else is looking at this, I do not how this code works.
    # I'm really really hoping it doesn't break.
    # Good luck!

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = parsed.xpath(
            ".//div[@id='{}']//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    # Start the dict.
    built_dict = {"game_stats": [], "overall_stats": {}, "average_stats": []}

    # Shortcut location for player level etc
    if not status or status.lower() != "public profile":
        hasrank = parsed.xpath(
            '//*[@id="overview-section"]/div/div/div/div/div[2]/div/div[3]/div'
        )
        if hasrank:
            comprank = int(hasrank[0].text)
        else:
            comprank = None
        built_dict["overall_stats"]["comprank"] = comprank
        return built_dict

    mast_head = parsed.xpath(".//div[@class='masthead-player']")[0]

    # Rank images are now based on 2 separate images. Prestige now also relies on 'player-rank' element
    prestige_stars = 0
    try:
        prestige_rank = mast_head.xpath(".//div[@class='player-rank']")[0]
        bg_image = [
            x for x in prestige_rank.values() if "background-image" in x
        ][0]
    except IndexError:
        # No stars
        prestige_stars = 0
    else:
        for key, val in PRESTIGE_STARS.items():
            if key in bg_image:
                prestige_stars = val
                # Adds a new dict key called "prestige_image". Left the old name below of "rank_image" for compatibility
                built_dict["overall_stats"]["prestige_image"] = bg_image.split(
                    "(")[1][:-1]
                break
            else:
                # Unknown prestige image
                prestige_stars = None

    # Extract the background-image from the styles.
    prestige_num = 0
    try:
        # Get the player-level base (border).
        prestige = mast_head.xpath(".//div[@class='player-level']")[0]
        bg_image = [x for x in prestige.values() if "background-image" in x][0]
    except IndexError:
        # Cannot find background-image.
        # Yikes!
        # Don't set a prestige.
        built_dict["overall_stats"]["prestige"] = 0
    else:
        for key, val in PRESTIGE_BORDERS.items():
            if key in bg_image:
                prestige_num = val
                built_dict["overall_stats"]["rank_image"] = bg_image.split(
                    "(")[1][:-1]
                break
        else:
            # Unknown rank image
            prestige_num = None

    # If we have prestige values, return them. Otherwise, return None
    if prestige_num is not None and prestige_stars is not None:
        built_dict["overall_stats"]["prestige"] = prestige_num + prestige_stars
    else:
        built_dict["overall_stats"]["prestige"] = None

    # Parse out the HTML.
    level = int(prestige.findall(".//div")[0].text)
    built_dict["overall_stats"]["level"] = level

    # Get and parse out endorsement level.
    endorsement_level = int(
        mast_head.xpath(
            ".//div[@class='EndorsementIcon-tooltip']/div[@class='u-center']")
        [0].text)
    built_dict["overall_stats"]["endorsement_level"] = endorsement_level

    # Get endorsement circle.
    endorsement_icon_inner = mast_head.xpath(
        ".//div[@class='endorsement-level']/div[@class='EndorsementIcon']/div["
        "@class='EndorsementIcon-inner']")[0]

    # Get individual endorsement segments.
    names = ("shotcaller", "teammate", "sportsmanship")
    for name in names:
        try:
            endorsement_value = endorsement_icon_inner.findall(
                f".//svg[@class='EndorsementIcon-border EndorsementIcon-border--{name}']"
            )[0].get("data-value")
        except:  # TODO: don't do this...
            endorsement_value = 0

        val = float(endorsement_value)
        built_dict["overall_stats"][f"endorsement_{name}"] = val

    # Get comp rank.
    try:
        for role in mast_head.xpath(".//div[@class='competitive-rank']")[0]:
            role_img = role.findall(
                ".//img[@class='competitive-rank-role-icon']")[0]
            role_img_src = role_img.values()[1]
            role_str = ""
            for key, val in role_data_img_src.items():
                if key in role_img_src:
                    role_str = val
                    break
            built_dict["overall_stats"][role_str +
                                        "_role_image"] = role_img_src

            tier_img = role.findall(
                ".//img[@class='competitive-rank-tier-icon']")[0]
            tier_img_src = tier_img.values()[1]
            tier_str = ""
            for key, val in tier_data_img_src.items():
                if key in tier_img_src:
                    tier_str = val
                    break
            built_dict["overall_stats"][role_str +
                                        "_tier_image"] = tier_img_src

            built_dict["overall_stats"][role_str + "_tier"] = tier_str

            hasrank = role.findall(".//div[@class='competitive-rank-level']")
            if hasrank:
                comprank = int(hasrank[0].text)
            else:
                comprank = None
            built_dict["overall_stats"][role_str + "_comprank"] = comprank

    except IndexError as exc:
        print(str(exc))
    finally:
        for role in ["tank", "damage", "support"]:
            if role + "_role_image" not in built_dict["overall_stats"]:
                built_dict["overall_stats"][role + "_role_image"] = None
            if role + "_tier_image" not in built_dict["overall_stats"]:
                built_dict["overall_stats"][role + "_tier_image"] = None
            if role + "_tier" not in built_dict["overall_stats"]:
                built_dict["overall_stats"][role + "_tier"] = None
            if role + "_comprank" not in built_dict["overall_stats"]:
                built_dict["overall_stats"][role + "_comprank"] = None

    # Fetch Avatar
    avatar_root = mast_head.find(".//img[@class='player-portrait']")
    # some profiles don't have an avatar url?
    # it's just a <img class="player-portrait"> ???
    if avatar_root is not None:
        try:
            avatar_url = avatar_root.attrib["src"]
        except KeyError:
            avatar_url = None
    else:
        avatar_url = None

    built_dict["overall_stats"]["avatar"] = avatar_url

    if mode == "competitive":
        # the competitive overview is under a div with id='competitive'
        # and the right category
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='competitive']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # No stats
            return None
    elif mode == "quickplay":
        try:
            stat_groups = parsed.xpath(
                ".//div[@id='quickplay']"
                "//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
            )[0]
        except IndexError:
            # User has no stats...
            return None
    else:
        # how else to handle fallthrough case?
        stat_groups = parsed.xpath(
            ".//div[@data-group-id='stats' and @data-category-id='0x02E00000FFFFFFFF']"
        )[0]

    # Highlight specific stat groups.
    try:
        game_box = stat_groups[3]
    except IndexError:
        try:
            game_box = stat_groups[2]  # I guess use 2?
        except IndexError:
            # edge cases...
            # we can't really extract any more stats
            # so we do an early return
            return {}

    # Calculate the wins, losses, and win rate.
    try:
        wins = int(
            game_box.xpath(".//text()[. = 'Games Won']/../..")[0]
            [1].text.replace(",", ""))
    except IndexError:
        # weird edge case
        wins = 0
    g = game_box.xpath(".//text()[. = 'Games Played']/../..")
    if len(g) < 1:
        # Blizzard f****d up, temporary quick fix for #70
        games, losses = None, None
    else:
        games = int(g[0][1].text.replace(",", ""))

    if mode == "competitive":
        try:
            losses = int(
                game_box.xpath(".//text()[. = 'Games Lost']/../..")[0]
                [1].text.replace(",", ""))
            ties = int(
                game_box.xpath(".//text()[. = 'Games Tied']/../..")[0]
                [1].text.replace(",", ""))
        except IndexError:
            # Sometimes the losses and ties don't exist.
            # I'm not 100% as to what causes this, but it might be because there are no ties.
            # In this case, just set ties to 0, and calculate losses manually.
            ties = 0
            # Quickplay shit.
            # Goddamnit blizzard.
            if games is None:
                losses = 0
                games = 0
                wins = 0
            else:
                # Competitive stats do have these values (for now...)
                losses = games - wins

        if games == 0 or games == ties:
            wr = 0
        else:
            wr = round((wins / (games - ties)) * 100, 2)

        built_dict["overall_stats"]["ties"] = ties
        built_dict["overall_stats"]["games"] = games
        built_dict["overall_stats"]["losses"] = losses
        built_dict["overall_stats"]["win_rate"] = wr

    # Update the dictionary.
    built_dict["overall_stats"]["wins"] = wins

    # Build a dict using the stats.
    game_stats = {}
    average_stats = {}
    rolling_average_stats = {}

    for subbox in stat_groups:
        trs = subbox.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text
            # Try and parse out the value. It might be a time!
            # If so, try and extract the time.
            nvl = util.try_extract(value)

            if "average" in name.lower():
                name = name.replace("_average", "_avg")
                into = average_stats
            elif "_avg_per_10_min" in name.lower():
                # 2017-08-03 - calculate rolling averages.
                name = name.lower().replace("_avg_per_10_min", "")
                into = rolling_average_stats
            else:
                into = game_stats

            # Correct Blizzard Singular Plural Bug
            if "_plural_" in name:
                name = util.correct_plural_name(name, nvl)

            into[name] = nvl

    # Manually add the KPD.
    try:
        game_stats["kpd"] = round(
            game_stats["eliminations"] / game_stats["deaths"], 2)
    except KeyError:
        # They don't have any eliminations/deaths.
        # Set the KPD to 0.0.
        # See: #106
        game_stats["kpd"] = 0

    built_dict["game_stats"] = game_stats
    built_dict["average_stats"] = average_stats
    built_dict["rolling_average_stats"] = rolling_average_stats
    built_dict["competitive"] = mode == "competitive"

    if "games" not in built_dict["overall_stats"]:
        # manually calculate it
        # 2017-07-04 - changed to use eliminations
        # since damage done gave a bit of a stupid amount
        # 2017-07-11 - changed to cycle some averages
        average_keys = ("eliminations", "healing_done", "final_blows",
                        "objective_kills")
        for key in average_keys:
            try:
                total = built_dict["game_stats"][key]
                avg = built_dict["average_stats"][key + "_avg"]
            except KeyError:
                continue
            else:
                got = True
                break
        else:
            got = False

        if got:
            games = int(total // avg)

            losses = games - built_dict["overall_stats"]["wins"]
            built_dict["overall_stats"]["games"] = games
            built_dict["overall_stats"]["losses"] = losses
            built_dict["overall_stats"]["win_rate"] = round(
                (built_dict["overall_stats"]["wins"] / games) * 100, 2)
        else:
            # lol make them up
            built_dict["overall_stats"]["games"] = 0
            built_dict["overall_stats"]["losses"] = 0
            built_dict["overall_stats"]["win_rate"] = 0

    return built_dict
Example #19
0
def bl_parse_hero_data(parsed: etree._Element, mode="quickplay"):
    # Start the dict.
    built_dict = {}

    _root = parsed.xpath(".//div[@id='{}']".format(
        "competitive" if mode == "competitive" else "quickplay"))
    if not _root:
        return None

    try:
        # XPath for the `u-align-center` h6 which signifies there's no data.
        no_data = _root[0].xpath(
            ".//ul/h6[@class='u-align-center']".format(mode))[0]
    except IndexError:
        pass
    else:
        if no_data.text.strip(
        ) == "We don't have any data for this account in this mode yet.":
            return None

    for hero_name, requested_hero_div_id in hero_data_div_ids.items():
        n_dict = {}
        _stat_groups = _root[0].xpath(
            ".//div[@data-group-id='stats' and @data-category-id='{0}']".
            format(requested_hero_div_id))

        if not _stat_groups:
            continue

        stat_groups = _stat_groups[0]
        _average_stats = {}

        _t_d = {}
        hero_specific_box = stat_groups[0]
        trs = hero_specific_box.findall(".//tbody/tr")
        # Update the dict with [0]: [1]
        for subval in trs:
            name, value = util.sanitize_string(subval[0].text), subval[1].text

            # Put averages into average_stats
            if "average" in name:
                into = _average_stats
            else:
                into = _t_d
            nvl = util.try_extract(value)
            into[name] = nvl

        n_dict["hero_stats"] = _t_d

        _t_d = {}
        for subbox in stat_groups[1:]:
            trs = subbox.findall(".//tbody/tr")
            # Update the dict with [0]: [1]
            for subval in trs:
                name, value = util.sanitize_string(
                    subval[0].text), subval[1].text
                # Put averages into average_stats
                if "average" in name:
                    into = _average_stats
                else:
                    into = _t_d
                nvl = util.try_extract(value)
                into[name] = nvl

        n_dict["general_stats"] = _t_d
        n_dict["average_stats"] = _average_stats

        built_dict[hero_name] = n_dict

    return built_dict