1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Merge branch 'osu'

# Conflicts:
#	pyproject.toml
This commit is contained in:
Steffo 2020-07-23 01:20:48 +02:00
commit c41ae6b116
9 changed files with 343 additions and 3 deletions

View file

@ -2,7 +2,7 @@
[tool.poetry] [tool.poetry]
name = "royalpack" name = "royalpack"
version = "5.13.1" version = "5.13.2"
description = "A Royalnet command pack for the Royal Games community" description = "A Royalnet command pack for the Royal Games community"
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"] authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
license = "AGPL-3.0+" license = "AGPL-3.0+"
@ -23,8 +23,8 @@
riotwatcher = "^3.0.0" riotwatcher = "^3.0.0"
royalspells = "^3.2" royalspells = "^3.2"
steam = "*" steam = "*"
sqlalchemy = "^1.3.18" sqlalchemy = "^1.3.18"
bcrypt = "^3.1.7" bcrypt = "^3.1.7"
[tool.poetry.dependencies.royalnet] [tool.poetry.dependencies.royalnet]
version = "~5.10.4" version = "~5.10.4"

View file

@ -38,6 +38,7 @@ from .steampowered import SteampoweredCommand
from .treasure import TreasureCommand from .treasure import TreasureCommand
from .trivia import TriviaCommand from .trivia import TriviaCommand
from .userinfo import UserinfoCommand from .userinfo import UserinfoCommand
from .osu import OsuCommand
# Enter the commands of your Pack here! # Enter the commands of your Pack here!
available_commands = [ available_commands = [
@ -80,6 +81,7 @@ available_commands = [
TreasureCommand, TreasureCommand,
TriviaCommand, TriviaCommand,
UserinfoCommand, UserinfoCommand,
OsuCommand,
] ]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__

125
royalpack/commands/osu.py Normal file
View file

@ -0,0 +1,125 @@
from typing import *
import itsdangerous
import aiohttp
from royalnet.backpack import tables as rbt
import royalnet.commands as rc
import royalnet.utils as ru
from .abstract.linker import LinkerCommand
from ..types import Updatable
from ..tables import Osu
from ..stars.api_auth_login_osu import ApiAuthLoginOsuStar
class OsuCommand(LinkerCommand):
name = "osu"
description = "Connetti e sincronizza il tuo account di osu!"
@property
def client_id(self):
return self.config[self.name]['client_id']
@property
def client_secret(self):
return self.config[self.name]['client_secret']
@property
def base_url(self):
return self.config['base_url']
@property
def secret_key(self):
return self.config['secret_key']
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Osu]:
return user.osu
async def get_updatables(self, session) -> List[Osu]:
return await ru.asyncify(session.query(self.alchemy.get(Osu)).all)
async def create(self,
session,
user: rbt.User,
args: rc.CommandArgs,
data: Optional[rc.CommandData] = None) -> Optional[Osu]:
serializer = itsdangerous.URLSafeSerializer(self.secret_key, salt="osu")
# TODO: Ensure the chat the link is being sent in is secure!!!
await data.reply("🔑 [b]Login necessario[/b]\n"
f"[url=https://osu.ppy.sh/oauth/authorize"
f"?client_id={self.client_id}"
f"&redirect_uri={self.base_url}{ApiAuthLoginOsuStar.path}"
f"&response_type=code"
f"&state={serializer.dumps(user.uid)}]"
f"Connetti account di osu! a {user.username}"
f"[/url]")
return None
async def update(self, session, obj: Osu, change: Callable[[str, Any], Awaitable[None]]):
await obj.refresh_if_expired(client_id=self.client_id,
client_secret=self.client_secret,
base_url=self.base_url,
path=ApiAuthLoginOsuStar.path)
async with aiohttp.ClientSession(headers={"Authorization": f"Bearer {obj.access_token}"}) as session:
async with session.get("https://osu.ppy.sh/api/v2/me/osu") as response:
m = await response.json()
obj.avatar_url = m["avatar_url"]
obj.username = m["username"]
if "statistics" in m:
await change("standard_pp", m["statistics"].get("pp"))
async with session.get("https://osu.ppy.sh/api/v2/me/taiko") as response:
m = await response.json()
if "statistics" in m:
await change("taiko_pp", m["statistics"].get("pp"))
async with session.get("https://osu.ppy.sh/api/v2/me/fruits") as response:
m = await response.json()
if "statistics" in m:
await change("catch_pp", m["statistics"].get("pp"))
async with session.get("https://osu.ppy.sh/api/v2/me/mania") as response:
m = await response.json()
if "statistics" in m:
await change("mania_pp", m["statistics"].get("pp"))
async def on_increase(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
if attribute == "standard_pp":
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu![/i]! Congratulazioni!")
elif attribute == "taiko_pp":
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!taiko[/i]! Congratulazioni!")
elif attribute == "catch_pp":
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!catch[/i]! Congratulazioni!")
elif attribute == "mania_pp":
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!mania[/i]! Congratulazioni!")
async def on_unchanged(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
pass
async def on_decrease(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
if attribute == "standard_pp":
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu![/i].")
elif attribute == "taiko_pp":
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!taiko[/i].")
elif attribute == "catch_pp":
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!catch[/i].")
elif attribute == "mania_pp":
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!mania[/i].")
async def on_first(self, session, obj: Osu, attribute: str, old: None, new: Any) -> None:
if attribute == "standard_pp":
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu![/i]!")
elif attribute == "taiko_pp":
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!taiko[/i]!")
elif attribute == "catch_pp":
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!catch[/i]!")
elif attribute == "mania_pp":
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!mania[/i]!")
async def on_reset(self, session, obj: Osu, attribute: str, old: Any, new: None) -> None:
if attribute == "standard_pp":
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu![/i].")
elif attribute == "taiko_pp":
await self.notify(f" ⬜️[b]{obj.user}[/b] non è più classificato su [i]osu!taiko[/i].")
elif attribute == "catch_pp":
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu!catch[/i].")
elif attribute == "mania_pp":
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu!mania[/i].")

View file

@ -13,6 +13,7 @@ from .api_cvstats_avg import ApiCvstatsAvgStar
from .api_user_ryg import ApiUserRygStar from .api_user_ryg import ApiUserRygStar
from .api_user_ryg_list import ApiUserRygListStar from .api_user_ryg_list import ApiUserRygListStar
from .api_user_avatar import ApiUserAvatarStar from .api_user_avatar import ApiUserAvatarStar
from .api_auth_login_osu import ApiAuthLoginOsuStar
# Enter the PageStars of your Pack here! # Enter the PageStars of your Pack here!
available_page_stars = [ available_page_stars = [
@ -30,6 +31,7 @@ available_page_stars = [
ApiUserRygStar, ApiUserRygStar,
ApiUserRygListStar, ApiUserRygListStar,
ApiUserAvatarStar, ApiUserAvatarStar,
ApiAuthLoginOsuStar,
] ]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__

View file

@ -0,0 +1,99 @@
import royalnet.utils as ru
import royalnet.backpack.tables as rbt
import royalnet.constellation.api as rca
import royalnet.constellation.api.apierrors as rcae
import itsdangerous
import aiohttp
import aiohttp.client_exceptions
import datetime
from ..types import oauth_refresh
from ..tables import Osu, FiorygiTransaction
class ApiAuthLoginOsuStar(rca.ApiStar):
path = "/api/auth/login/osu/v1"
parameters = {
"get": {
"code": "The code returned by the osu! API.",
"state": "(Optional) The state payload generated by the osu! command to link a new account. "
"If missing, just login."
}
}
auth = {
"get": False,
}
tags = ["auth"]
@property
def client_id(self):
return self.config['osu']['client_id']
@property
def client_secret(self):
return self.config['osu']['client_secret']
@property
def base_url(self):
return self.config['base_url']
@property
def secret_key(self):
return self.config['secret_key']
@rca.magic
async def get(self, data: rca.ApiData) -> ru.JSON:
"""Login to Royalnet with your osu! account."""
OsuT = self.alchemy.get(Osu)
TokenT = self.alchemy.get(rbt.Token)
code = data.str("code")
state = data.str("state", optional=True)
if state is not None:
serializer = itsdangerous.URLSafeSerializer(self.config["secret_key"], salt="osu")
uid = serializer.loads(state)
user = await rbt.User.find(self.alchemy, data.session, uid)
else:
user = None
try:
t = await oauth_refresh(url="https://osu.ppy.sh/oauth/token",
client_id=self.client_id,
client_secret=self.client_secret,
redirect_uri=f"{self.base_url}{self.path}",
refresh_code=code)
except aiohttp.client_exceptions.ClientResponseError:
raise rca.ForbiddenError("osu! API returned an error in the OAuth token exchange")
async with aiohttp.ClientSession(headers={"Authorization": f"Bearer {t['access_token']}"}) as session:
async with session.get("https://osu.ppy.sh/api/v2/me/") as response:
m = await response.json()
if user is not None:
osu = OsuT(
user=user,
access_token=t["access_token"],
refresh_token=t["refresh_token"],
expiration_date=datetime.datetime.now() + datetime.timedelta(seconds=t["expires_in"]),
osu_id=m["id"],
username=m["username"]
)
data.session.add(osu)
else:
osu = await ru.asyncify(
data.session.query(OsuT).filter_by(osu_id=m["id"]).all
)
if osu is None:
raise rcae.ForbiddenError("Unknown osu! account")
user = osu.user
token: rbt.Token = TokenT.generate(alchemy=self.alchemy, user=user, expiration_delta=datetime.timedelta(days=7))
data.session.add(token)
await data.session_commit()
return token.json()

View file

@ -18,6 +18,7 @@ from .mmevents import MMEvent
from .mmresponse import MMResponse from .mmresponse import MMResponse
from .cvstats import Cvstats from .cvstats import Cvstats
from .treasure import Treasure from .treasure import Treasure
from .osu import Osu
# Enter the tables of your Pack here! # Enter the tables of your Pack here!
available_tables = [ available_tables = [
@ -40,6 +41,7 @@ available_tables = [
MMResponse, MMResponse,
Cvstats, Cvstats,
Treasure, Treasure,
Osu,
] ]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__

94
royalpack/tables/osu.py Normal file
View file

@ -0,0 +1,94 @@
from typing import *
import aiohttp
import datetime
from sqlalchemy import *
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declared_attr
from ..types import Updatable, oauth_refresh
# noinspection PyAttributeOutsideInit
class Osu(Updatable):
__tablename__ = "osu"
@declared_attr
def user_id(self):
return Column(Integer, ForeignKey("users.uid"))
@declared_attr
def user(self):
return relationship("User", backref=backref("osu"))
@declared_attr
def access_token(self):
return Column(String, nullable=False)
@declared_attr
def refresh_token(self):
return Column(String, nullable=False)
@declared_attr
def expiration_date(self):
return Column(DateTime, nullable=False)
@declared_attr
def osu_id(self):
return Column(Integer, primary_key=True)
@declared_attr
def username(self):
return Column(String, nullable=False)
@declared_attr
def avatar_url(self):
return Column(String, nullable=False)
@declared_attr
def standard_pp(self):
return Column(Float)
@declared_attr
def taiko_pp(self):
return Column(Float)
@declared_attr
def catch_pp(self):
return Column(Float)
@declared_attr
def mania_pp(self):
return Column(Float)
async def refresh(self, *, client_id, client_secret, base_url, path):
j = await oauth_refresh(url="https://osu.ppy.sh/oauth/token",
client_id=client_id,
client_secret=client_secret,
redirect_uri=f"{base_url}{path}",
refresh_code=self.refresh_token)
self.access_token = j["access_token"]
self.refresh_token = j["refresh_token"]
self.expiration_date = datetime.datetime.now() + datetime.timedelta(seconds=j["expires_in"])
async def refresh_if_expired(self, *, client_id, client_secret, base_url, path):
if datetime.datetime.now() >= self.expiration_date:
await self.refresh(client_id=client_id, client_secret=client_secret, base_url=base_url, path=path)
def json(self) -> dict:
return {
"osu_id": self.osu_id,
"username": self.username,
"avatar_url": self.avatar_url,
"standard": {
"pp": self.standard_pp,
},
"taiko": {
"pp": self.taiko_pp,
},
"catch": {
"pp": self.catch_pp,
},
"mania": {
"pp": self.mania_pp,
},
}

View file

@ -11,6 +11,7 @@ from .brawlhallametal import BrawlhallaMetal
from .brawlhallarank import BrawlhallaRank from .brawlhallarank import BrawlhallaRank
from .pollmood import PollMood from .pollmood import PollMood
from .updatable import Updatable from .updatable import Updatable
from .oauth_refresh import oauth_refresh
__all__ = [ __all__ = [
@ -28,4 +29,5 @@ __all__ = [
"BrawlhallaTier", "BrawlhallaTier",
"PollMood", "PollMood",
"Updatable", "Updatable",
"oauth_refresh",
] ]

View file

@ -0,0 +1,14 @@
import aiohttp
async def oauth_refresh(*, url, client_id, client_secret, redirect_uri, refresh_code):
async with aiohttp.ClientSession() as session:
async with session.post(url, data={
"client_id": client_id,
"client_secret": client_secret,
"code": refresh_code,
"grant_type": "authorization_code",
"redirect_uri": redirect_uri
}) as response:
j = await response.json()
return j