2016-10-29 16:16:25 +13:00
|
|
|
from .utils import config
|
|
|
|
from .utils import checks
|
|
|
|
from .utils import images
|
|
|
|
|
|
|
|
from discord.ext import commands
|
|
|
|
import discord
|
|
|
|
|
|
|
|
import aiohttp
|
|
|
|
|
|
|
|
# https://github.com/ppy/osu-api/wiki
|
|
|
|
base_url = 'https://osu.ppy.sh/api/'
|
|
|
|
MAX_RETRIES = 5
|
|
|
|
|
2016-11-29 21:05:22 +13:00
|
|
|
|
2016-10-29 16:16:25 +13:00
|
|
|
class Osu:
|
|
|
|
def __init__(self, bot):
|
|
|
|
self.bot = bot
|
|
|
|
self.headers = {'User-Agent': config.user_agent}
|
|
|
|
self.key = config.osu_key
|
|
|
|
|
|
|
|
async def _request(self, payload, endpoint):
|
|
|
|
"""Handles requesting to the API"""
|
|
|
|
|
|
|
|
# Format the URL we'll need based on the base_url, and the endpoint we want to hit
|
|
|
|
url = "{}{}".format(base_url, endpoint)
|
|
|
|
|
|
|
|
# Check if our key was added, if it wasn't, add it
|
|
|
|
key = payload.get('k', self.key)
|
|
|
|
payload['k'] = key
|
|
|
|
|
|
|
|
# Attempt to connect up to our max retries
|
|
|
|
for x in range(MAX_RETRIES):
|
|
|
|
try:
|
|
|
|
async with aiohttp.get(url, headers=self.headers, params=payload) as r:
|
|
|
|
# If we failed to connect, attempt again
|
|
|
|
if r.status != 200:
|
|
|
|
continue
|
|
|
|
|
|
|
|
data = await r.json()
|
|
|
|
return data
|
|
|
|
# If any error happened when making the request, attempt again
|
|
|
|
except:
|
|
|
|
continue
|
|
|
|
|
|
|
|
async def find_beatmap(self, query):
|
|
|
|
"""Finds a beatmap ID based on the first match of searching a beatmap"""
|
|
|
|
pass
|
|
|
|
|
2016-10-30 16:33:47 +13:00
|
|
|
async def get_beatmap(self, b_id):
|
|
|
|
"""Gets beatmap info based on the ID provided"""
|
|
|
|
params = {'b': b_id}
|
|
|
|
endpoint = 'get_beatmaps'
|
2016-10-30 16:38:00 +13:00
|
|
|
data = await self._request(params, endpoint)
|
|
|
|
try:
|
|
|
|
return data[0]
|
|
|
|
except IndexError:
|
|
|
|
return None
|
2016-10-30 16:33:47 +13:00
|
|
|
|
2016-10-29 16:16:25 +13:00
|
|
|
@commands.group(pass_context=True, invoke_without_command=True)
|
|
|
|
@checks.custom_perms(send_messages=True)
|
|
|
|
async def osu(self, ctx):
|
|
|
|
pass
|
|
|
|
|
2016-10-30 16:17:17 +13:00
|
|
|
@osu.command(name='scores', aliases=['score'], pass_context=True)
|
2016-10-30 16:16:16 +13:00
|
|
|
@checks.custom_perms(send_messages=True)
|
|
|
|
async def osu_user_scores(self, ctx, user, song=1):
|
|
|
|
"""Used to get the top scores for a user
|
|
|
|
You can provide either your Osu ID or your username
|
|
|
|
However, if your username is only numbers, this will confuse the API
|
|
|
|
If you have only numbers in your username, you will need to provide your ID
|
2016-11-29 17:55:55 +13:00
|
|
|
This will by default return the top song, provide song [up to 100] to get that song, in order from best to worst
|
|
|
|
|
|
|
|
EXAMPLE: !osu MyUsername 5
|
|
|
|
RESULT: Info about your 5th best song"""
|
2016-10-30 16:16:16 +13:00
|
|
|
|
2016-10-30 16:41:12 +13:00
|
|
|
await self.bot.say("Looking up your Osu information...")
|
2016-10-30 16:16:16 +13:00
|
|
|
# To make this easy for the user, it's indexed starting at 1, so lets subtract by 1
|
|
|
|
song -= 1
|
|
|
|
# Make sure the song is not negative however, if so set to 0
|
|
|
|
if song < 0:
|
|
|
|
song = 0
|
|
|
|
|
|
|
|
# A list of the possible values we'll receive, that we want to display
|
2016-10-30 16:33:47 +13:00
|
|
|
wanted_info = ['username', 'maxcombo', 'count300', 'count100', 'count50', 'countmiss',
|
2016-11-29 21:05:22 +13:00
|
|
|
'perfect', 'enabled_mods', 'date', 'rank' 'pp', 'beatmap_title', 'beatmap_version',
|
|
|
|
'max_combo', 'artist', 'difficulty']
|
2016-10-30 16:16:16 +13:00
|
|
|
|
|
|
|
# A couple of these aren't the best names to display, so setup a map to change these just a little bit
|
|
|
|
key_map = {'maxcombo': 'combo',
|
2016-11-29 21:05:22 +13:00
|
|
|
'count300': '300 hits',
|
|
|
|
'count100': '100 hits',
|
|
|
|
'count50': '50 hits',
|
|
|
|
'countmiss': 'misses',
|
|
|
|
'perfect': 'got_max_combo'}
|
2016-10-30 16:16:16 +13:00
|
|
|
|
|
|
|
params = {'u': user,
|
2016-11-29 21:05:22 +13:00
|
|
|
'limit': 100}
|
2016-10-30 16:16:16 +13:00
|
|
|
# The endpoint that we're accessing to get this informatin
|
|
|
|
endpoint = 'get_user_best'
|
|
|
|
data = await self._request(params, endpoint)
|
|
|
|
|
|
|
|
try:
|
|
|
|
data = data[song]
|
|
|
|
except IndexError:
|
|
|
|
if len(data) == 0:
|
|
|
|
await self.bot.say("I could not find any top songs for the user {}".format(user))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
data = data[len(data) - 1]
|
|
|
|
|
2016-10-30 16:33:47 +13:00
|
|
|
# There's a little bit more info that we need, some info specific to the beatmap.
|
|
|
|
# Due to this we'll need to make a second request
|
|
|
|
beatmap_data = await self.get_beatmap(data.get('beatmap_id', None))
|
|
|
|
|
|
|
|
# Lets add the extra data we want
|
|
|
|
data['beatmap_title'] = beatmap_data.get('title')
|
|
|
|
data['beatmap_version'] = beatmap_data.get('version')
|
|
|
|
data['max_combo'] = beatmap_data.get('max_combo')
|
|
|
|
data['artist'] = beatmap_data.get('artist')
|
|
|
|
# Lets round this, no need for such a long number
|
2016-10-30 16:38:58 +13:00
|
|
|
data['difficulty'] = round(float(beatmap_data.get('difficultyrating')), 2)
|
2016-10-30 16:33:47 +13:00
|
|
|
|
2016-10-30 16:16:16 +13:00
|
|
|
# Now lets create our dictionary needed to create the image
|
|
|
|
# The dict comprehension we're using is simpler than it looks, it's simply they key: value
|
|
|
|
# If the key is in our wanted_info list
|
|
|
|
# We also get the wanted value from the key_map if it exists, using the key itself if it doesn't
|
|
|
|
# We then title it and replace _ with a space to ensure nice formatting
|
2016-10-31 10:54:58 +13:00
|
|
|
fmt = [(key_map.get(k, k).title().replace('_', ' '), v) for k, v in data.items() if k in wanted_info]
|
2016-10-30 16:16:16 +13:00
|
|
|
|
|
|
|
# Attempt to create our banner and upload that
|
|
|
|
# If we can't find the images needed, or don't have permissions, just send a message instead
|
|
|
|
try:
|
|
|
|
banner = await images.create_banner(ctx.message.author, "Osu User Stats", fmt)
|
|
|
|
await self.bot.upload(banner)
|
|
|
|
except (FileNotFoundError, discord.Forbidden):
|
2016-10-31 10:54:58 +13:00
|
|
|
_fmt = "\n".join("{}: {}".format(k, r) for k, r in fmt)
|
2016-10-30 16:16:16 +13:00
|
|
|
await self.bot.say("```\n{}```".format(_fmt))
|
|
|
|
|
|
|
|
@osu.command(name='user', pass_context=True)
|
2016-10-29 16:16:25 +13:00
|
|
|
@checks.custom_perms(send_messages=True)
|
|
|
|
async def osu_user_info(self, ctx, *, user):
|
|
|
|
"""Used to get information about a specific user
|
|
|
|
You can provide either your Osu ID or your username
|
|
|
|
However, if your username is only numbers, this will confuse the API
|
2016-11-29 17:55:55 +13:00
|
|
|
If you have only numbers in your username, you will need to provide your ID
|
|
|
|
|
|
|
|
EXAMPLE: !osu user MyUserName
|
|
|
|
RESULT: Info about your user"""
|
2016-10-29 16:16:25 +13:00
|
|
|
|
2016-10-30 16:41:12 +13:00
|
|
|
await self.bot.say("Looking up your Osu information...")
|
2016-10-29 16:16:25 +13:00
|
|
|
# A list of the possible values we'll receive, that we want to display
|
2016-10-30 16:33:47 +13:00
|
|
|
wanted_info = ['username', 'playcount', 'ranked_score', 'pp_rank', 'level', 'pp_country_rank',
|
2016-11-29 21:05:22 +13:00
|
|
|
'accuracy', 'country', 'pp_country_rank', 'count_rank_s', 'count_rank_a']
|
2016-10-29 16:16:25 +13:00
|
|
|
|
|
|
|
# A couple of these aren't the best names to display, so setup a map to change these just a little bit
|
|
|
|
key_map = {'playcount': 'play_count',
|
2016-11-29 21:05:22 +13:00
|
|
|
'count_rank_ss': 'total_SS_ranks',
|
|
|
|
'count_rank_s': 'total_s_ranks',
|
|
|
|
'count_rank_a': 'total_a_ranks'}
|
2016-10-29 16:16:25 +13:00
|
|
|
|
|
|
|
# The paramaters that we'll send to osu to get the information needed
|
|
|
|
params = {'u': user}
|
|
|
|
# The endpoint that we're accessing to get this informatin
|
|
|
|
endpoint = 'get_user'
|
|
|
|
data = await self._request(params, endpoint)
|
|
|
|
|
|
|
|
# Make sure we found a result, we should only find one with the way we're searching
|
|
|
|
try:
|
|
|
|
data = data[0]
|
|
|
|
except IndexError:
|
|
|
|
await self.bot.say("I could not find anyone with the user name/id of {}".format(user))
|
|
|
|
return
|
|
|
|
|
|
|
|
# Now lets create our dictionary needed to create the image
|
|
|
|
# The dict comprehension we're using is simpler than it looks, it's simply they key: value
|
|
|
|
# If the key is in our wanted_info list
|
|
|
|
# We also get the wanted value from the key_map if it exists, using the key itself if it doesn't
|
|
|
|
# We then title it and replace _ with a space to ensure nice formatting
|
|
|
|
fmt = {key_map.get(k, k).title().replace('_', ' '): v for k, v in data.items() if k in wanted_info}
|
|
|
|
|
|
|
|
# Attempt to create our banner and upload that
|
|
|
|
# If we can't find the images needed, or don't have permissions, just send a message instead
|
|
|
|
try:
|
2016-10-29 16:34:50 +13:00
|
|
|
banner = await images.create_banner(ctx.message.author, "Osu User Stats", fmt)
|
2016-10-29 16:16:25 +13:00
|
|
|
await self.bot.upload(banner)
|
|
|
|
except (FileNotFoundError, discord.Forbidden):
|
|
|
|
_fmt = "\n".join("{}: {}".format(k, r) for k, r in fmt.items())
|
|
|
|
await self.bot.say("```\n{}```".format(_fmt))
|
|
|
|
|
2016-11-29 21:05:22 +13:00
|
|
|
|
2016-10-29 16:16:25 +13:00
|
|
|
def setup(bot):
|
2016-10-29 16:25:47 +13:00
|
|
|
bot.add_cog(Osu(bot))
|