2023-03-22 21:28:15 +09:00
|
|
|
import asyncio
|
|
|
|
import os
|
|
|
|
from urllib import parse
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
import aiohttp
|
|
|
|
import uvicorn
|
2023-03-22 21:49:19 +09:00
|
|
|
from cryptography.fernet import Fernet
|
2023-03-22 21:28:15 +09:00
|
|
|
from dotenv import load_dotenv
|
|
|
|
from fastapi import Cookie, FastAPI, HTTPException, Request, Response
|
|
|
|
from fastapi.responses import RedirectResponse
|
|
|
|
from linked_roles import LinkedRolesOAuth2, RoleConnection
|
|
|
|
|
|
|
|
|
|
|
|
load_dotenv()
|
|
|
|
app = FastAPI(
|
|
|
|
title="Achievement Promotion with Steam - Discord",
|
|
|
|
description="This API is used to verify that you have achievement of the game and give you a role.",
|
|
|
|
version="1.0.0",
|
|
|
|
openapi_url=None
|
|
|
|
)
|
|
|
|
client = LinkedRolesOAuth2(
|
|
|
|
client_id=os.getenv("CLIENT_ID"),
|
|
|
|
client_secret=os.getenv("CLIENT_SECRET"),
|
|
|
|
redirect_uri=f"{os.getenv('REDIRECT_URI')}/discord",
|
|
|
|
token=os.getenv("BOT_TOKEN"),
|
2023-03-22 23:23:54 +09:00
|
|
|
scopes=("identify", "role_connections.write"),
|
2023-03-22 21:28:15 +09:00
|
|
|
state=os.getenv("COOKIE_SECRET")
|
|
|
|
)
|
2023-03-22 23:39:01 +09:00
|
|
|
key = Fernet.generate_key()
|
|
|
|
fn = Fernet(key)
|
2023-03-22 21:28:15 +09:00
|
|
|
|
|
|
|
def encrypt(text: str) -> str:
|
|
|
|
return fn.encrypt(text.encode())
|
|
|
|
|
2023-03-22 23:43:33 +09:00
|
|
|
def decrypt(token: str) -> str:
|
2023-03-22 23:55:22 +09:00
|
|
|
return fn.decrypt(token).decode()
|
2023-03-22 21:28:15 +09:00
|
|
|
|
|
|
|
async def async_list(values: list) -> Any:
|
|
|
|
for value in values:
|
|
|
|
yield value
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
@app.on_event('startup')
|
|
|
|
async def startup():
|
|
|
|
await client.start()
|
|
|
|
|
|
|
|
@app.on_event('shutdown')
|
|
|
|
async def shutdown():
|
|
|
|
await client.close()
|
|
|
|
|
2023-03-22 23:39:01 +09:00
|
|
|
@app.get("/")
|
|
|
|
async def root():
|
|
|
|
return RedirectResponse(url="https://github.com/CloudToys/Achievement-Promotion")
|
|
|
|
|
|
|
|
@app.get("/verify")
|
2023-03-22 21:28:15 +09:00
|
|
|
async def link():
|
|
|
|
steam_openid_url = "https://steamcommunity.com/openid/login"
|
|
|
|
u = {
|
|
|
|
'openid.ns': "http://specs.openid.net/auth/2.0",
|
|
|
|
'openid.identity': "http://specs.openid.net/auth/2.0/identifier_select",
|
|
|
|
'openid.claimed_id': "http://specs.openid.net/auth/2.0/identifier_select",
|
|
|
|
'openid.mode': "checkid_setup",
|
|
|
|
'openid.return_to': f"{os.getenv('REDIRECT_URI')}/steam",
|
2023-03-22 23:13:42 +09:00
|
|
|
'openid.realm': f"{os.getenv('REDIRECT_URI')}/steam"
|
2023-03-22 21:28:15 +09:00
|
|
|
}
|
|
|
|
query_string = parse.urlencode(u)
|
|
|
|
auth_url = steam_openid_url + "?" + query_string
|
|
|
|
return RedirectResponse(auth_url)
|
|
|
|
|
|
|
|
@app.get('/callback/steam')
|
2023-03-22 23:35:54 +09:00
|
|
|
async def setup(request: Request):
|
2023-03-22 23:17:06 +09:00
|
|
|
valid = await validate(dict(request.query_params))
|
2023-03-22 21:28:15 +09:00
|
|
|
if not valid:
|
|
|
|
raise HTTPException(status_code=404, detail="We can't verify that you have Steam profile.")
|
|
|
|
url = client.get_oauth_url()
|
2023-03-22 23:35:54 +09:00
|
|
|
response = RedirectResponse(url=url)
|
2023-03-22 23:57:39 +09:00
|
|
|
openid = request.query_params.get("openid.claimed_id").split("/")[-1]
|
|
|
|
token = encrypt(openid)
|
2023-03-22 23:54:54 +09:00
|
|
|
token = token.decode()
|
|
|
|
response.set_cookie(key="steam_id", value=token, max_age=1800)
|
2023-03-22 23:35:54 +09:00
|
|
|
return response
|
2023-03-22 21:28:15 +09:00
|
|
|
|
2023-03-22 23:17:06 +09:00
|
|
|
async def validate(data: dict) -> bool:
|
2023-03-22 21:28:15 +09:00
|
|
|
base = "https://steamcommunity.com/openid/login"
|
2023-03-23 00:04:26 +09:00
|
|
|
if "openid.mode" not in data:
|
|
|
|
return False
|
2023-03-22 21:28:15 +09:00
|
|
|
params = {
|
|
|
|
"openid.assoc_handle": data["openid.assoc_handle"],
|
|
|
|
"openid.sig": data["openid.sig"],
|
|
|
|
"openid.ns": data["openid.ns"],
|
|
|
|
"openid.mode": "check_authentication"
|
|
|
|
}
|
|
|
|
data.update(params)
|
|
|
|
data["openid.mode"] = "check_authentication"
|
|
|
|
data["openid.signed"] = data["openid.signed"]
|
|
|
|
|
|
|
|
session = aiohttp.ClientSession()
|
|
|
|
r = await session.post(base, data=data)
|
2023-03-22 23:18:44 +09:00
|
|
|
text = await r.text()
|
2023-03-22 21:28:15 +09:00
|
|
|
|
2023-03-22 23:18:44 +09:00
|
|
|
if "is_valid:true" in text:
|
2023-03-22 21:28:15 +09:00
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
@app.get('/callback/discord')
|
2023-03-22 23:30:08 +09:00
|
|
|
async def update_metadata(response: Response, code: str, steam_id: str = Cookie()):
|
2023-03-22 21:28:15 +09:00
|
|
|
token = await client.get_access_token(code)
|
|
|
|
user = await client.fetch_user(token)
|
|
|
|
|
|
|
|
if user is None:
|
|
|
|
raise HTTPException(status_code=404, detail="We can't verify that you have Discord profile.")
|
|
|
|
|
|
|
|
steam_id = decrypt(steam_id)
|
|
|
|
session = aiohttp.ClientSession()
|
|
|
|
r = await session.get(f"http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?appid={os.getenv('STEAM_GAME_ID')}&key={os.getenv('STEAM_API_KEY')}&steamid={steam_id}&l=en")
|
|
|
|
res = await r.json()
|
|
|
|
data = res["playerstats"]
|
|
|
|
if data["success"] is False:
|
|
|
|
if data["error"] == "Profile is not public":
|
|
|
|
raise HTTPException(status_code=403, detail="We can't verify that you have achievement because your profile is private.")
|
|
|
|
else:
|
|
|
|
raise HTTPException(status_code=500, detail=data["error"])
|
|
|
|
|
2023-03-22 21:39:14 +09:00
|
|
|
abc = await session.get(f"http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={os.getenv('STEAM_API_KEY')}&steamids={steam_id}")
|
|
|
|
abc = await abc.json()
|
2023-03-22 21:28:15 +09:00
|
|
|
role = await user.fetch_role_connection()
|
|
|
|
if role is None:
|
2023-03-22 21:39:14 +09:00
|
|
|
role = RoleConnection(platform_name=f"Steam - {data['gameName']}", platform_username=abc["response"]["players"][0]["personaname"])
|
2023-03-22 21:28:15 +09:00
|
|
|
success = 0
|
|
|
|
total = len(data["achievements"])
|
|
|
|
async for achieve in async_list(data["achievements"]):
|
2023-03-23 17:35:18 +09:00
|
|
|
if achieve["apiname"] == "honor_roll":
|
|
|
|
role.add_or_edit_metadata(key="honor_roll", value=True if achieve["achieved"] == 1 else False)
|
|
|
|
if achieve["apiname"] == "go_to_bed":
|
|
|
|
role.add_or_edit_metadata(key="go_to_bed", value=True if achieve["achieved"] == 1 else False)
|
|
|
|
if achieve["apiname"] == "new_day":
|
|
|
|
role.add_or_edit_metadata(key="new_day", value=True if achieve["achieved"] == 1 else False)
|
2023-03-22 21:28:15 +09:00
|
|
|
if achieve["achieved"] == 1:
|
|
|
|
success += 1
|
2023-03-23 00:01:55 +09:00
|
|
|
percentage = round((success / total) * 100)
|
2023-03-22 21:28:15 +09:00
|
|
|
role.add_or_edit_metadata(key="percentage", value=percentage)
|
2023-03-23 17:35:18 +09:00
|
|
|
if success == total:
|
2023-03-23 00:03:21 +09:00
|
|
|
role.add_or_edit_metadata(key="complete", value=True)
|
2023-03-23 17:35:18 +09:00
|
|
|
else:
|
|
|
|
role.add_or_edit_metadata(key="complete", value=False)
|
2023-03-22 21:28:15 +09:00
|
|
|
await user.edit_role_connection(role)
|
|
|
|
response.set_cookie(key="steam_id", value="", max_age=1)
|
2023-03-23 00:07:11 +09:00
|
|
|
return "Successfully connected! Now go back to Discord and check result."
|
2023-03-22 21:28:15 +09:00
|
|
|
|
|
|
|
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=4278)
|