Exemplo n.º 1
0
def format_new_score(mode: api.GameMode, score: dict, beatmap: dict, rank: int, stream_url: str=None):
    """ Format any score. There should be a member name/mention in front of this string. """
    acc = calculate_acc(mode, score)
    return (
        "set a new best (`#{pos}/{limit} +{diff:.2f}pp`) on *{artist} - {title}* **[{version}] {stars:.2f}\u2605**\n"
        "**{pp}pp, {rank} {scoreboard_rank}+{mods}**"
        "```diff\n"
        "  acc     300s    100s    50s     miss    combo\n"
        "{sign} {acc:<8.2%}{count300:<8}{count100:<8}{count50:<8}{countmiss:<8}{maxcombo}{max_combo}```"
        "**Profile**: <https://osu.ppy.sh/u/{user_id}>\n"
        "**Beatmap**: <https://osu.ppy.sh/b/{beatmap_id}>"
        "{live}"
    ).format(
        limit=score_request_limit,
        sign="!" if acc == 1 else ("+" if score["perfect"] == "1" else "-"),
        mods=Mods.format_mods(int(score["enabled_mods"])),
        acc=acc,
        artist=beatmap["artist"].replace("*", "\*").replace("_", "\_"),
        title=beatmap["title"].replace("*", "\*").replace("_", "\_"),
        version=beatmap["version"],
        stars=float(beatmap["difficultyrating"]),
        max_combo="/{}".format(beatmap["max_combo"]) if mode in (api.GameMode.Standard, api.GameMode.Catch) else "",
        scoreboard_rank="#{} ".format(rank) if rank else "",
        live="\n**Watch live @** <{}>".format(stream_url) if stream_url else "",
        **score
    )
Exemplo n.º 2
0
def format_new_score(mode: api.GameMode,
                     score: dict,
                     beatmap: dict,
                     rank: int,
                     stream_url: str = None):
    """ Format any score. There should be a member name/mention in front of this string. """
    acc = calculate_acc(mode, score)
    return (
        "[*{artist} - {title} [{version}]*]({host}b/{beatmap_id})\n"
        "**{pp}pp {stars:.2f}\u2605, {rank} {scoreboard_rank}+{mods}**"
        "```diff\n"
        "  acc     300s   100s   50s    miss   combo\n"
        "{sign} {acc:<8.2%}{count300:<7}{count100:<7}{count50:<7}{countmiss:<7}{maxcombo}{max_combo}```"
        "{live}").format(
            host=host,
            sign="!" if acc == 1 else
            ("+" if score["perfect"] == "1" else "-"),
            mods=Mods.format_mods(int(score["enabled_mods"])),
            acc=acc,
            artist=beatmap["artist"].replace("*", "\*").replace("_", "\_"),
            title=beatmap["title"].replace("*", "\*").replace("_", "\_"),
            version=beatmap["version"],
            stars=float(beatmap["difficultyrating"]),
            max_combo="/{}".format(beatmap["max_combo"])
            if mode in (api.GameMode.Standard, api.GameMode.Catch) else "",
            scoreboard_rank="#{} ".format(rank) if rank else "",
            live="**Watch live @** <{}>\n".format(stream_url)
            if stream_url else "",
            **score)
Exemplo n.º 3
0
async def format_new_score(mode: api.GameMode,
                           score: dict,
                           beatmap: dict,
                           rank: int = None,
                           member: discord.Member = None):
    """ Format any score. There should be a member name/mention in front of this string. """
    acc = calculate_acc(mode, score)
    return (
        "[{i}{artist} - {title} [{version}]{i}]({host}b/{beatmap_id})\n"
        "**{pp}pp {stars:.2f}\u2605, {rank} {scoreboard_rank}+{mods}**"
        "```diff\n"
        "  acc     300s   100s   50s    miss   combo\n"
        "{sign} {acc:<8.2%}{count300:<7}{count100:<7}{count50:<7}{countmiss:<7}{maxcombo}{max_combo}```"
        "{live}").format(
            host=host,
            sign="!" if acc == 1 else
            ("+" if score["perfect"] == "1" else "-"),
            mods=Mods.format_mods(int(score["enabled_mods"])),
            acc=acc,
            artist=beatmap["artist"].replace("_", "\_"),
            title=beatmap["title"].replace("_", "\_"),
            i="*" if "*" not in beatmap["artist"] + beatmap["title"] else
            "",  # Escaping asterisk doesn't work in italics
            version=beatmap["version"],
            stars=float(beatmap["difficultyrating"]),
            max_combo="/{}".format(beatmap["max_combo"])
            if mode in (api.GameMode.Standard, api.GameMode.Catch) else "",
            scoreboard_rank="#{} ".format(rank) if rank else "",
            live=await format_stream(member, score, beatmap) if member else "",
            **score)
Exemplo n.º 4
0
async def format_stream(member: discord.Member, score: dict, beatmap: dict):
    """ Format the stream url and a VOD button when possible. """
    stream_url = getattr(member.game, "url", None)
    if not stream_url:
        return ""

    # Add the stream url and return immediately if twitch is not setup
    text = "**Watch live @** <{}>".format(stream_url)
    if not twitch.client_id:
        return text + "\n"

    # Try getting the vod information of the current stream
    try:
        twitch_id = await twitch.get_id(member)
        vod_request = await twitch.request(
            "channels/{}/videos".format(twitch_id),
            limit=1,
            broadcast_type="archive",
            sort="time")
        assert vod_request["_total"] >= 1
    except:
        print_exc()
        return text + "\n"

    vod = vod_request["videos"][0]

    # Find the timestamp of where the play would have started without pausing the game
    score_created = datetime.strptime(score["date"], "%Y-%m-%d %H:%M:%S")
    vod_created = datetime.strptime(
        vod["created_at"], "%Y-%m-%dT%H:%M:%SZ") + timedelta(hours=8)  # UTC-8
    beatmap_length = int(beatmap["total_length"])

    # Convert beatmap length when speed mods are enabled
    mods = Mods.list_mods(int(score["enabled_mods"]))
    if Mods.DT in mods or Mods.NC in mods:
        beatmap_length /= 1.5
    elif Mods.HT in mods:
        beatmap_length /= 0.75

    # Get the timestamp in the VOD when the score was created
    timestamp_score_created = (score_created - vod_created).total_seconds()
    timestamp_play_started = timestamp_score_created - beatmap_length

    # Add the vod url with timestamp to the formatted text
    text += " | **[`Video of this play :)`]({0}?t={1}s)**\n".format(
        vod["url"], int(timestamp_play_started))
    return text
Exemplo n.º 5
0
async def format_minimal_score(mode: api.GameMode, score: dict, beatmap: dict,
                               rank: int, member: discord.Member):
    """ Format any osu! score with minimal content.
    There should be a member name/mention in front of this string. """
    acc = calculate_acc(mode, score)
    return (
        "[*{artist} - {title} [{version}]*]({host}b/{beatmap_id})\n"
        "**{pp}pp {stars:.2f}\u2605, {rank} {acc:.2%} {scoreboard_rank}+{mods}**"
        "{live}").format(
            host=host,
            mods=Mods.format_mods(int(score["enabled_mods"])),
            acc=acc,
            artist=beatmap["artist"].replace("*", "\*").replace("_", "\_"),
            title=beatmap["title"].replace("*", "\*").replace("_", "\_"),
            version=beatmap["version"],
            stars=float(beatmap["difficultyrating"]),
            scoreboard_rank="#{} ".format(rank) if rank else "",
            live=await format_stream(member, score, beatmap),
            **score)
Exemplo n.º 6
0
def format_minimal_score(mode: api.GameMode, score: dict, beatmap: dict, rank: int, stream_url: str=None):
    """ Format any osu! score with minimal content.
    There should be a member name/mention in front of this string. """
    acc = calculate_acc(mode, score)
    return (
        "set a new best on *{artist} - {title}* **[{version}] {stars:.2f}\u2605**\n"
        "**{pp}pp, {rank} {acc:.2%} {scoreboard_rank}+{mods}**\n"
        "**Beatmap**: <https://osu.ppy.sh/b/{beatmap_id}>"
        "{live}"
    ).format(
        mods=Mods.format_mods(int(score["enabled_mods"])),
        acc=acc,
        artist=beatmap["artist"].replace("*", "\*").replace("_", "\_"),
        title=beatmap["title"].replace("*", "\*").replace("_", "\_"),
        version=beatmap["version"],
        stars=float(beatmap["difficultyrating"]),
        scoreboard_rank="#{} ".format(rank) if rank else "",
        live="\n**Watch live @** <{}>".format(stream_url) if stream_url else "",
        **score
    )
Exemplo n.º 7
0
async def get_potential_pp(score,
                           beatmap,
                           member: discord.Member,
                           score_pp: float,
                           use_acc: bool = False):
    """ Returns the potential pp or None if it shouldn't display """
    potential_pp = None

    # Find the potentially gained pp in standard when not FC
    if get_mode(member.id) is api.GameMode.Standard and get_update_mode(member.id) is not UpdateModes.PP \
            and int(score["maxcombo"]) < int(beatmap["max_combo"]):
        options = ["+" + Mods.format_mods(int(score["enabled_mods"]))]

        if use_acc:
            options.append("{acc:.2%}".format(acc=calculate_acc(
                api.GameMode.Standard, score, exclude_misses=True)))
        else:
            options.append(score["count100"] + "x100")
            options.append(score["count50"] + "x50")

        try:
            pp_stats = await calculate_pp(
                "https://osu.ppy.sh/b/{}".format(score["beatmap_id"]),
                *options)
            potential_pp = pp_stats.pp
        except:
            pass

        # Drop this info whenever the potential pp gain is negative.
        #     The osu! API does not provide info on sliderbreak count and missed sliderend count, which results
        #     in faulty calculation (very often negative relatively). Therefore, I will conclude that the score
        #     was actually an FC and has missed sliderends when the gain is negative.
        if potential_pp - score_pp <= 0:
            potential_pp = None

    return potential_pp
Exemplo n.º 8
0
async def notify_pp(member_id: str, data: dict):
    """ Notify any differences in pp and post the scores + rank/pp gained. """
    # Only update pp when there is actually a difference
    if "old" not in data:
        return

    # Get the difference in pp since the old data
    old, new = data["old"], data["new"]
    pp_diff = get_diff(old, new, "pp_raw")

    # If the difference is too small or nothing, move on
    if pp_threshold > pp_diff > -pp_threshold:
        return

    rank_diff = -int(get_diff(old, new, "pp_rank"))
    country_rank_diff = -int(get_diff(old, new, "pp_country_rank"))
    accuracy_diff = get_diff(old, new, "accuracy")  # Percent points difference

    member = data["member"]
    mode = get_mode(member_id)
    update_mode = get_update_mode(member_id)
    m = ""
    potential_pp = None

    # Since the user got pp they probably have a new score in their own top 100
    # If there is a score, there is also a beatmap
    if update_mode is UpdateModes.PP:
        score = None
    else:
        score = await get_new_score(member_id)

    # If a new score was found, format the score
    if score:
        beatmap_search = await api.get_beatmaps(b=int(score["beatmap_id"]),
                                                m=mode.value,
                                                a=1)
        beatmap = api.lookup_beatmap(beatmap_search)
        stream_url = getattr(member.game, "url", None)

        # There might not be any events
        scoreboard_rank = None
        if new["events"]:
            scoreboard_rank = api.rank_from_events(new["events"],
                                                   score["beatmap_id"])

        # Find the potentially gained pp in standard when not FC
        if mode is api.GameMode.Standard and update_mode is not UpdateModes.PP and int(
                score["maxcombo"]) < int(beatmap["max_combo"]):
            options = [
                score["count100"] + "x100", score["count50"] + "x50",
                "+" + Mods.format_mods(int(score["enabled_mods"]))
            ]
            try:
                potential_pp = await calculate_pp(
                    "https://osu.ppy.sh/b/{}".format(score["beatmap_id"]),
                    *options)
            except:
                pass

        if update_mode is UpdateModes.Minimal:
            m += format_minimal_score(mode, score, beatmap, scoreboard_rank,
                                      stream_url) + "\n"
        else:
            m += format_new_score(mode, score, beatmap, scoreboard_rank,
                                  stream_url)

    # Always add the difference in pp along with the ranks
    m += format_user_diff(mode, pp_diff, rank_diff, country_rank_diff,
                          accuracy_diff, old["country"], new)

    # Send the message to all servers
    for server in client.servers:
        member = server.get_member(member_id)
        channels = get_notify_channels(server, "score")
        if not member or not channels:
            continue

        primary_server = get_primary_server(member.id)
        is_primary = True if primary_server is None else (
            True if primary_server == server.id else False)

        # Format the url link and the username
        user_url = get_user_url(member.id)
        name = "{member.mention} [`{ripple}{name}`]({url})".format(
            member=member,
            name=new["username"],
            url=user_url,
            ripple="ripple: " if new["ripple"] else "")

        embed = discord.Embed(color=member.color, url=user_url)
        embed.description = m

        # The top line of the format will differ depending on whether we found a score or not
        if score:
            embed.description = "**{0} set a new best `(#{pos}/{1} +{diff:.2f}pp)` on**\n".format(
                name, score_request_limit, **score) + m
        else:
            embed.description = name + "\n" + m

        # Add potential pp in the footer
        if potential_pp:
            embed.set_footer(text="Potential: {0:,}pp, {1:+.2f}pp".format(
                potential_pp, potential_pp - float(score["pp"])))

        for i, channel in enumerate(channels):
            try:
                await client.send_message(channel, embed=embed)

                # In the primary server and if the user sets a score, send a mention and delete it
                # This will only mention in the first channel of the server
                if use_mentions_in_scores and score and i == 0 and is_primary:
                    mention = await client.send_message(
                        channel, member.mention)
                    await client.delete_message(mention)
            except discord.Forbidden:
                pass