Initial
This commit is contained in:
commit
5f102a7820
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
CLIENT_NAME="YourAPINme (Dev)/1.0"
|
||||
VRCHAT_API_BASE="https://api.vrchat.cloud/api/1"
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
@"
|
||||
__pycache__/
|
||||
*.pyc
|
||||
venv/
|
||||
*.log
|
||||
"@ | Out-File -Encoding utf8 .gitignore
|
||||
.env
|
||||
data/*
|
112
README.md
Normal file
112
README.md
Normal file
@ -0,0 +1,112 @@
|
||||
<img src="./data/static/logo.png" width="300" style="margin-top: 10px;" draggable="false">
|
||||
|
||||
A lightweight, fast, and secure **FastAPI** proxy server for the [VRChat API](https://vrchat.community/getting-started).
|
||||
Designed to handle authentication, token management, and provide cached access to public and private VRChat data endpoints.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- **Seamless VRChat account authentication** with support for 2FA (email & TOTP)
|
||||
- Automated token storage and expiry management (configurable cache duration)
|
||||
- Public API endpoints to fetch VRChat groups & users info
|
||||
- Private API endpoints secured by VRChat auth tokens
|
||||
- Written in Python with FastAPI and HTTPX for async requests
|
||||
- Auto environment setup with virtual environment creation & dependency installation
|
||||
- Ready to deploy on any server with Python 3.8+ (tested with YunoHost)
|
||||
|
||||
---
|
||||
|
||||
## 💡 Why this project?
|
||||
|
||||
VRChat’s official API requires complex login flows and token management that can be cumbersome to implement for your apps or bots.
|
||||
**K-API** simplifies this by handling authentication and caching internally, exposing easy REST endpoints for your applications.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- Python 3.8 or higher installed globally
|
||||
- Git (optional)
|
||||
|
||||
### Installation & Running
|
||||
|
||||
Clone this repository:
|
||||
|
||||
```bash
|
||||
git clone https://git.kvs.fyi/kryscau/VRChatAPI.git
|
||||
# You can use the mirror with Github with: https://github.com/kryscau/VRChatAPI.git
|
||||
cd VRChatAPI
|
||||
```
|
||||
|
||||
Run the included Python bootstrap script to create and activate the virtual environment, install dependencies, authenticate your VRChat account, and start the server:
|
||||
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
> This script will prompt for your VRChat username, password, and 2FA code if required.
|
||||
> Tokens are stored securely and refreshed automatically every 30 days.
|
||||
|
||||
### Access the API
|
||||
|
||||
- Public endpoint example:
|
||||
`GET /api/public/groups/{group_id}`
|
||||
Returns info about a VRChat group.
|
||||
|
||||
- Private endpoint example (requires authentication or specific permission in VRChat [join group, bans perms, ...]):
|
||||
`GET /api/private/users/{user_id}`
|
||||
Returns private user data accessible with your token.
|
||||
|
||||
Explore interactive docs at:
|
||||
`http://127.0.0.1:8000/docs`
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Privacy
|
||||
|
||||
- Your VRChat credentials and tokens are stored **locally** in JSON files inside the `data/auth/` directory.
|
||||
- No credentials or tokens are ever sent to third-party servers.
|
||||
- Use HTTPS and proper firewall rules when deploying publicly.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
/app
|
||||
/api # FastAPI route modules (public/private)
|
||||
/data/auth # Token storage (auto-generated)
|
||||
/prelaunch # Authentication helper scripts
|
||||
run.py # Python bootstrapper script
|
||||
requirements.txt # Python dependencies
|
||||
README.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contribution
|
||||
|
||||
Feel free to open issues or submit pull requests.
|
||||
Feature requests and bug reports are welcome!
|
||||
|
||||
---
|
||||
|
||||
## ⚡ License
|
||||
|
||||
MIT License © 2025 Kryscau (K-API)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
- Add WebSocket support for real-time VRChat events
|
||||
- Implement caching layers with Redis or similar
|
||||
- Dockerize for easy container deployments
|
||||
- Add OAuth support for multi-user API proxies
|
||||
|
||||
---
|
||||
|
||||
Made with ❤️ by [Kryscau](https://kryscau.github.io).
|
162
app/api/vrchat_groups.py
Normal file
162
app/api/vrchat_groups.py
Normal file
@ -0,0 +1,162 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import httpx
|
||||
import json
|
||||
from app.env import CLIENT_NAME, API_BASE, TOKEN_FILE
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def load_token():
|
||||
if not TOKEN_FILE.exists():
|
||||
return None
|
||||
with open(TOKEN_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
@router.get("/groups/{group_id}")
|
||||
async def get_groups(group_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
params = {
|
||||
"includeRoles": "true",
|
||||
"purpose": "group"
|
||||
}
|
||||
url = f"{API_BASE}/groups/{group_id}"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies, params=params)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch group info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/groups/{group_id}/instances")
|
||||
async def get_groups_instances(group_id: str, user_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
url = f"{API_BASE}/groups/{group_id}/instances"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch groups instances info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/groups/{group_id}/posts")
|
||||
async def get_groups_posts(group_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
params = {
|
||||
"n": "10",
|
||||
"offset": "0",
|
||||
"publicOnly": False
|
||||
}
|
||||
url = f"{API_BASE}/groups/{group_id}/posts"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies, params=params)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch groups posts info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
|
||||
@router.get("/groups/{group_id}/bans")
|
||||
async def get_groups_bans(group_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
params = {
|
||||
"n": "51",
|
||||
"offset": "0"
|
||||
}
|
||||
url = f"{API_BASE}/groups/{group_id}/bans"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies, params=params)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch groups bans info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/groups/{group_id}/roles")
|
||||
async def get_groups_roles(group_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
url = f"{API_BASE}/groups/{group_id}/roles"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch groups roles info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/groups/{group_id}/members")
|
||||
async def get_groups_members(group_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
params = {
|
||||
"n": "25",
|
||||
"offset": "0"
|
||||
}
|
||||
url = f"{API_BASE}/groups/{group_id}/members"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies, params=params)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch groups members info: {r.text}")
|
||||
|
||||
return r.json()
|
108
app/api/vrchat_users.py
Normal file
108
app/api/vrchat_users.py
Normal file
@ -0,0 +1,108 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import httpx
|
||||
import json
|
||||
from app.env import CLIENT_NAME, API_BASE, TOKEN_FILE
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def load_token():
|
||||
if not TOKEN_FILE.exists():
|
||||
return None
|
||||
with open(TOKEN_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
@router.get("/users/{user_id}")
|
||||
async def get_user(user_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
url = f"{API_BASE}/users/{user_id}"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch user info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/users/{user_id}/friends/status")
|
||||
async def get_user_friend_status(user_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
url = f"{API_BASE}/users/{user_id}/friendStatus"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch user friend status info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/users/{user_id}/worlds")
|
||||
async def get_user_worlds(user_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
params = {
|
||||
"releaseStatus": "public",
|
||||
"sort": "updated",
|
||||
"order": "descending",
|
||||
"userId": user_id,
|
||||
"n": "100",
|
||||
"offset": "0"
|
||||
}
|
||||
url = f"{API_BASE}/worlds"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies, params=params)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch user worlds info: {r.text}")
|
||||
|
||||
return r.json()
|
||||
|
||||
@router.get("/users/{user_id}/groups")
|
||||
async def get_user_groups(user_id: str):
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Token not found, please authenticate first")
|
||||
|
||||
auth_cookie = token.get("auth_cookie")
|
||||
if not auth_cookie:
|
||||
raise HTTPException(status_code=401, detail="Auth cookie missing in token")
|
||||
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
cookies = {"auth": auth_cookie}
|
||||
url = f"{API_BASE}/users/{user_id}/groups"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(url, headers=headers, cookies=cookies)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(status_code=r.status_code, detail=f"Failed to fetch user groups info: {r.text}")
|
||||
|
||||
return r.json()
|
8
app/env.py
Normal file
8
app/env.py
Normal file
@ -0,0 +1,8 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
from pathlib import Path
|
||||
load_dotenv(dotenv_path=Path(__file__).parent.parent / ".env")
|
||||
|
||||
CLIENT_NAME = os.getenv("CLIENT_NAME", "default-client-name")
|
||||
API_BASE = os.getenv("VRCHAT_API_BASE", "https://api.vrchat.cloud/api/1")
|
||||
TOKEN_FILE = Path(os.getenv("TOKEN_FILE", "data/auth/account.json"))
|
32
app/main.py
Normal file
32
app/main.py
Normal file
@ -0,0 +1,32 @@
|
||||
from fastapi import FastAPI
|
||||
from app.api.vrchat_users import router as users
|
||||
from app.api.vrchat_groups import router as groups
|
||||
|
||||
app = FastAPI(
|
||||
title="K-API",
|
||||
description="""
|
||||
K-API is a fast, secure, and lightweight proxy API for VRChat.
|
||||
It handles authentication via VRChat’s official API, including 2FA support,
|
||||
and provides cached endpoints for user and group information retrieval.
|
||||
|
||||
This project is designed for developers who want a hassle-free way
|
||||
to integrate VRChat data into their apps without managing sessions or tokens manually.
|
||||
|
||||
Features:
|
||||
- Automatic token management with 2FA handling
|
||||
- Public and private VRChat data endpoints
|
||||
- Response caching for performance
|
||||
- Easy deployment on self-hosted servers (YunoHost compatible)
|
||||
|
||||
Built with FastAPI and async HTTPX for high performance and reliability.
|
||||
""",
|
||||
swagger_ui_parameters={"defaultModelsExpandDepth": -1},
|
||||
docs_url="/docs",
|
||||
redoc_url=None,
|
||||
openapi_url="/openapi.json",
|
||||
contact={"name": "Kryscau", "url": "https://vrchat.com/home/user/usr_323befe7-edbc-46fe-af9d-560f7e6b290c", "email": "kryscau@kvs.fyi" }
|
||||
)
|
||||
prefix = "/api"
|
||||
|
||||
app.include_router(users, prefix=prefix, tags=["Users"])
|
||||
app.include_router(groups, prefix=prefix, tags=["Groups"])
|
142
app/prelaunch/vrchat_auth.py
Normal file
142
app/prelaunch/vrchat_auth.py
Normal file
@ -0,0 +1,142 @@
|
||||
import httpx
|
||||
import base64
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
||||
from env import CLIENT_NAME, API_BASE, TOKEN_FILE
|
||||
|
||||
def save_token(data):
|
||||
data["created_at"] = datetime.now(timezone.utc).isoformat()
|
||||
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(TOKEN_FILE, "w") as f:
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
def load_token():
|
||||
if not TOKEN_FILE.exists():
|
||||
return None
|
||||
with open(TOKEN_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
created = datetime.fromisoformat(data.get("created_at", "2000-01-01T00:00:00+00:00"))
|
||||
if datetime.now(timezone.utc) - created > timedelta(days=30):
|
||||
print("⚠️ Token expired. Reconnection required.")
|
||||
return None
|
||||
return data
|
||||
|
||||
def verify_auth_cookie(auth_cookie):
|
||||
cookies = {"auth": auth_cookie}
|
||||
headers = {"User-Agent": CLIENT_NAME}
|
||||
with httpx.Client(base_url=API_BASE, cookies=cookies, headers=headers) as client:
|
||||
r = client.get("/auth")
|
||||
return r.status_code == 200 and r.json().get("ok", False)
|
||||
|
||||
def login():
|
||||
print("🔐 Connecting to VRChat")
|
||||
manual_username = input("Username: ")
|
||||
password = input("Password: ")
|
||||
|
||||
creds = f"{manual_username}:{password}"
|
||||
b64 = base64.b64encode(creds.encode()).decode()
|
||||
auth_header = f"Basic {b64}"
|
||||
|
||||
headers = {
|
||||
"Authorization": auth_header,
|
||||
"User-Agent": CLIENT_NAME
|
||||
}
|
||||
|
||||
with httpx.Client(base_url=API_BASE, headers=headers) as client:
|
||||
# 1 - First GET call to /auth/user with Basic Auth
|
||||
r = client.get("/auth/user")
|
||||
if r.status_code != 200:
|
||||
print("❌ Connection failed:", r.text)
|
||||
return None
|
||||
|
||||
data = r.json()
|
||||
|
||||
# 2 - Check 2FA
|
||||
if "requiresTwoFactorAuth" in data:
|
||||
mfa_types = data["requiresTwoFactorAuth"]
|
||||
print(f"🔐 2FA required: {mfa_types}")
|
||||
|
||||
# The Authorization header is removed for the following request
|
||||
client.headers.pop("Authorization", None)
|
||||
|
||||
if "otp" in mfa_types:
|
||||
code = input("Code 2FA (TOTP): ")
|
||||
verify_endpoint = "/auth/twofactorauth/verify"
|
||||
elif "emailOtp" in mfa_types:
|
||||
code = input("Code 2FA (email): ")
|
||||
verify_endpoint = "/auth/twofactorauth/emailotp/verify"
|
||||
else:
|
||||
print("❌ Unknown 2FA type:", mfa_types)
|
||||
return None
|
||||
|
||||
r2 = client.post(verify_endpoint, json={"code": code})
|
||||
|
||||
if r2.status_code != 200 or not r2.json().get("verified", False):
|
||||
print("❌ 2FA verification failed:", r2.text)
|
||||
return None
|
||||
print("✅ 2FA verified!")
|
||||
|
||||
# 3 - Repeat a GET /auth/user without Basic Auth to confirm the session
|
||||
r3 = client.get("/auth/user")
|
||||
if r3.status_code != 200:
|
||||
print("❌ Failed to fetch user data after 2FA:", r3.text)
|
||||
return None
|
||||
|
||||
data = r3.json()
|
||||
|
||||
# 4 - Retrieve auth cookie
|
||||
auth_cookie = None
|
||||
for cookie in client.cookies.jar:
|
||||
if cookie.name == "auth":
|
||||
auth_cookie = cookie.value
|
||||
break
|
||||
|
||||
if not auth_cookie:
|
||||
print("❌ Auth cookie not found after login.")
|
||||
return None
|
||||
|
||||
# 5 - Check cookie
|
||||
if not verify_auth_cookie(auth_cookie):
|
||||
print("❌ Auth cookie invalid.")
|
||||
return None
|
||||
|
||||
print("✅ Connected and verified.")
|
||||
|
||||
# 6 - Prepare the information to be backed up
|
||||
display_name = data.get("displayName", manual_username)
|
||||
user_id = data.get("id", "")
|
||||
|
||||
return {
|
||||
"manual_username": manual_username,
|
||||
"displayName": display_name,
|
||||
"user_id": user_id,
|
||||
"auth": b64,
|
||||
"auth_cookie": auth_cookie
|
||||
}
|
||||
|
||||
def get_or_create_token():
|
||||
token = load_token()
|
||||
if token:
|
||||
print("🔑 Found saved token, verifying...")
|
||||
if verify_auth_cookie(token.get("auth_cookie", "")):
|
||||
print("🟢 Token already valid.")
|
||||
return token
|
||||
else:
|
||||
print("⚠️ Saved token invalid, need to login again.")
|
||||
|
||||
new_token = login()
|
||||
if new_token:
|
||||
save_token(new_token)
|
||||
return new_token
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
token_data = get_or_create_token()
|
||||
if token_data:
|
||||
print("🔓 Auth ready. Token stored.")
|
||||
else:
|
||||
print("❌ Unable to obtain a valid token.")
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
httpx
|
||||
orjson
|
||||
python-dotenv
|
56
run.py
Normal file
56
run.py
Normal file
@ -0,0 +1,56 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import venv
|
||||
|
||||
VENV_DIR = "venv"
|
||||
REQ_FILE = "requirements.txt"
|
||||
PORT = "8000"
|
||||
|
||||
# INFORMATIONS : This file does not require “venv” to run, but make sure you have Python installed globally (py run.py on Windows, for example).
|
||||
# INFORMATIONS : This file does not require “venv” to run, but make sure you have Python installed globally (py run.py on Windows, for example).
|
||||
# INFORMATIONS : This file does not require “venv” to run, but make sure you have Python installed globally (py run.py on Windows, for example).
|
||||
# INFORMATIONS : This file does not require “venv” to run, but make sure you have Python installed globally (py run.py on Windows, for example).
|
||||
# INFORMATIONS : This file does not require “venv” to run, but make sure you have Python installed globally (py run.py on Windows, for example).
|
||||
|
||||
def create_venv():
|
||||
print("Creating virtual environment...")
|
||||
venv.create(VENV_DIR, with_pip=True)
|
||||
|
||||
def run_in_venv(cmd):
|
||||
if os.name == "nt":
|
||||
# Windows
|
||||
python_bin = os.path.join(VENV_DIR, "Scripts", "python.exe")
|
||||
else:
|
||||
# Unix
|
||||
python_bin = os.path.join(VENV_DIR, "bin", "python")
|
||||
full_cmd = [python_bin] + cmd
|
||||
result = subprocess.run(full_cmd)
|
||||
if result.returncode != 0:
|
||||
print(f"Command {cmd} failed.")
|
||||
sys.exit(result.returncode)
|
||||
|
||||
def install_requirements():
|
||||
print("Installing dependencies...")
|
||||
run_in_venv(["-m", "pip", "install", "--upgrade", "pip"])
|
||||
run_in_venv(["-m", "pip", "install", "-r", REQ_FILE])
|
||||
|
||||
def main():
|
||||
if not os.path.exists(VENV_DIR):
|
||||
create_venv()
|
||||
install_requirements()
|
||||
else:
|
||||
print("Virtual environment found.")
|
||||
|
||||
print("Running VRChat authentication script...")
|
||||
run_in_venv(["app/prelaunch/vrchat_auth.py"])
|
||||
|
||||
print("Starting FastAPI server...")
|
||||
|
||||
HOST = "0.0.0.0"
|
||||
RELOAD_FLAG = "--reload"
|
||||
|
||||
run_in_venv(["-m", "uvicorn", "app.main:app", "--host", HOST, "--port", PORT] + ([RELOAD_FLAG] if RELOAD_FLAG else []))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user