py wrapper
This commit is contained in:
parent
64f3960a1d
commit
e279c4a63f
20
nise-frontend/src/app/api-python/api-python.component.html
Normal file
20
nise-frontend/src/app/api-python/api-python.component.html
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="main term mb-2">
|
||||
<div class="fade-stuff">
|
||||
<button [routerLink]="['/docs']">< back to api docs</button>
|
||||
<h1 class="mb-4"># super duper official python library</h1>
|
||||
<p>if you're using python, you can download/copy the api wrapper into your project and get (more or less) type-safe methods.</p>
|
||||
<p>last update: <code>v20240612</code></p>
|
||||
<p><u>remember</u> to <code>pip install requests</code></p>
|
||||
<div class="text-center">
|
||||
<button (click)="copyToClipboard()">copy to clipboard</button>
|
||||
<button (click)="download()" style="margin-left: 8px">download .py</button>
|
||||
</div>
|
||||
|
||||
|
||||
<hr class="mt-4 mb-4">
|
||||
<h1>Example usage</h1>
|
||||
<pre style="font-family: monospace, monospace !important">{{ pythonUsage }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
59
nise-frontend/src/app/api-python/api-python.component.ts
Normal file
59
nise-frontend/src/app/api-python/api-python.component.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Observable} from "rxjs";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {RouterLink} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'app-api-python',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './api-python.component.html',
|
||||
styleUrl: './api-python.component.css'
|
||||
})
|
||||
export class ApiPythonComponent {
|
||||
|
||||
pythonScript!: string;
|
||||
pythonUsage!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getPythonScript().subscribe(
|
||||
data => this.pythonScript = data,
|
||||
error => console.error(error)
|
||||
);
|
||||
|
||||
this.getPythonScriptUsage().subscribe(
|
||||
data => this.pythonUsage = data,
|
||||
error => console.error(error)
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getPythonScript(): Observable<string> {
|
||||
return this.http.get('/assets/nise.py', { responseType: 'text' });
|
||||
}
|
||||
|
||||
getPythonScriptUsage(): Observable<string> {
|
||||
return this.http.get('/assets/nise_usage.py', { responseType: 'text' });
|
||||
}
|
||||
|
||||
|
||||
copyToClipboard(): void {
|
||||
navigator.clipboard.writeText(this.pythonScript)
|
||||
.then(() => alert('Copied to clipboard!'))
|
||||
.catch(() => alert('Failed to copy to clipboard!'));
|
||||
}
|
||||
|
||||
download(): void {
|
||||
const blob = new Blob([this.pythonScript], {type: 'text/plain'});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'nise.py';
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,3 +2,11 @@ section {
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 2px dotted rgba(224, 224, 224, 0.32);
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.py-wrapper:hover {
|
||||
background-color: rgba(139, 139, 139, 0.32);
|
||||
}
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
<div class="main term mb-2">
|
||||
<div class="fade-stuff">
|
||||
<h1 class="mb-4"># <span class="board">/api/</span> stuff</h1>
|
||||
<div class="alert mb-2 text-center cursor-pointer py-wrapper" [routerLink]="['/docs/py']">
|
||||
<p>
|
||||
<span style="padding: 6px; background-color: #2af171; color: #000000">NEW!</span> check out the python api
|
||||
wrapper
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
if you'd like to retrieve data from our database, you are invited to use the same endpoints meant for the frontend but in a programmatic way. <u>currently there's no rate limits.</u>
|
||||
</p>
|
||||
|
||||
<div class="alert mb-2 text-center">
|
||||
<p>
|
||||
if you have any issues related to CloudFlare, let me know on discord. currently all api requests are routed via CF so it's possible it might block certain IPs.
|
||||
|
||||
@ -11,6 +11,7 @@ import {ContributeComponent} from "./contribute/contribute.component";
|
||||
import {BanlistComponent} from "./banlist/banlist.component";
|
||||
import {ProfileComponent} from "./profile/profile.component";
|
||||
import {ApiComponent} from "./api/api.component";
|
||||
import {ApiPythonComponent} from "./api-python/api-python.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
|
||||
@ -33,6 +34,7 @@ const routes: Routes = [
|
||||
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
|
||||
|
||||
{path: 'docs', component: ApiComponent, title: '/api/ explanation'},
|
||||
{path: 'docs/py', component: ApiPythonComponent, title: 'python lib'},
|
||||
|
||||
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
||||
];
|
||||
|
||||
328
nise-frontend/src/assets/nise.py
Normal file
328
nise-frontend/src/assets/nise.py
Normal file
@ -0,0 +1,328 @@
|
||||
import datetime
|
||||
|
||||
import requests
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchPagination:
|
||||
currentPage: int
|
||||
pageSize: int
|
||||
totalPages: int
|
||||
totalResults: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScoreSearchResult:
|
||||
user_id: int
|
||||
user_username: str
|
||||
user_join_date: str
|
||||
user_country: str
|
||||
user_rank: int
|
||||
user_pp_raw: float
|
||||
user_accuracy: float
|
||||
user_playcount: int
|
||||
user_total_score: int
|
||||
user_ranked_score: int
|
||||
user_seconds_played: int
|
||||
user_count_300: int
|
||||
user_count_100: int
|
||||
user_count_50: int
|
||||
user_count_miss: int
|
||||
user_is_banned: bool
|
||||
id: int
|
||||
beatmap_id: int
|
||||
count_300: int
|
||||
count_100: int
|
||||
count_50: int
|
||||
count_miss: int
|
||||
date: str
|
||||
max_combo: int
|
||||
mods: int
|
||||
perfect: bool
|
||||
pp: float
|
||||
rank: str
|
||||
replay_id: int
|
||||
score: int
|
||||
ur: float
|
||||
frametime: float
|
||||
edge_hits: int
|
||||
snaps: int
|
||||
adjusted_ur: float
|
||||
mean_error: float
|
||||
error_variance: float
|
||||
error_standard_deviation: float
|
||||
minimum_error: int
|
||||
maximum_error: int
|
||||
error_range: int
|
||||
error_coefficient_of_variation: float
|
||||
error_kurtosis: float
|
||||
error_skewness: float
|
||||
keypresses_median_adjusted: float
|
||||
keypresses_standard_deviation_adjusted: float
|
||||
sliderend_release_median_adjusted: float
|
||||
sliderend_release_standard_deviation_adjusted: float
|
||||
beatmap_artist: str
|
||||
beatmap_beatmapset_id: int
|
||||
beatmap_creator: str
|
||||
beatmap_source: Optional[str]
|
||||
beatmap_star_rating: float
|
||||
beatmap_title: str
|
||||
beatmap_version: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScoreSearchResult:
|
||||
pagination: SearchPagination
|
||||
results: List[ScoreSearchResult]
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserSearchResult:
|
||||
user_id: int
|
||||
user_username: str
|
||||
user_join_date: str
|
||||
user_country: str
|
||||
user_rank: int
|
||||
user_pp_raw: float
|
||||
user_accuracy: float
|
||||
user_playcount: int
|
||||
user_total_score: int
|
||||
user_ranked_score: int
|
||||
user_seconds_played: int
|
||||
user_count_300: int
|
||||
user_count_100: int
|
||||
user_count_50: int
|
||||
user_count_miss: int
|
||||
user_is_banned: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserSearchResult:
|
||||
pagination: SearchPagination
|
||||
results: List[UserSearchResult]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorDistribution:
|
||||
countMiss: float
|
||||
count300: float
|
||||
count100: float
|
||||
count50: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReplayData:
|
||||
replay_id: int
|
||||
user_id: int
|
||||
username: str
|
||||
date: str
|
||||
beatmap_id: int
|
||||
beatmap_beatmapset_id: int
|
||||
beatmap_artist: str
|
||||
beatmap_title: str
|
||||
beatmap_star_rating: float
|
||||
beatmap_creator: str
|
||||
beatmap_version: str
|
||||
beatmap_max_combo: int
|
||||
beatmap_total_length: int
|
||||
beatmap_bpm: float
|
||||
beatmap_accuracy: float
|
||||
beatmap_ar: float
|
||||
beatmap_cs: float
|
||||
beatmap_drain: float
|
||||
beatmap_count_circles: int
|
||||
beatmap_count_sliders: int
|
||||
beatmap_count_spinners: int
|
||||
score: int
|
||||
mods: List[str]
|
||||
rank: str
|
||||
ur: float
|
||||
adjusted_ur: float
|
||||
frametime: int
|
||||
snaps: int
|
||||
hits: int
|
||||
mean_error: float
|
||||
error_variance: float
|
||||
error_standard_deviation: float
|
||||
minimum_error: float
|
||||
maximum_error: float
|
||||
error_range: float
|
||||
error_coefficient_of_variation: float
|
||||
error_kurtosis: float
|
||||
error_skewness: float
|
||||
comparable_samples: int
|
||||
comparable_mean_error: float
|
||||
comparable_error_variance: float
|
||||
comparable_error_standard_deviation: float
|
||||
comparable_minimum_error: float
|
||||
comparable_maximum_error: float
|
||||
comparable_error_range: float
|
||||
comparable_error_coefficient_of_variation: float
|
||||
comparable_error_kurtosis: float
|
||||
comparable_error_skewness: float
|
||||
comparable_adjusted_ur: float
|
||||
pp: float
|
||||
perfect: bool
|
||||
max_combo: int
|
||||
count_300: int
|
||||
count_100: int
|
||||
count_50: int
|
||||
count_miss: int
|
||||
error_distribution: Dict[str, ErrorDistribution]
|
||||
similar_scores: List[Any]
|
||||
charts: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserDetails:
|
||||
user_id: int
|
||||
username: str
|
||||
rank: int
|
||||
pp_raw: float
|
||||
join_date: str
|
||||
seconds_played: int
|
||||
country: str
|
||||
country_rank: Optional[int]
|
||||
playcount: int
|
||||
suspicious_scores: int
|
||||
stolen_replays: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class QueueDetails:
|
||||
isProcessing: bool
|
||||
lastCompletedUpdate: Optional[str]
|
||||
canUpdate: bool
|
||||
progressCurrent: Optional[int]
|
||||
progressTotal: Optional[int]
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
user_details: UserDetails
|
||||
queue_details: QueueDetails
|
||||
suspicious_scores: List[dict] = field(default_factory=list)
|
||||
similar_replays: List[dict] = field(default_factory=list)
|
||||
total_scores: int = 0
|
||||
is_banned: bool = False
|
||||
approximate_ban_date: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SuspiciousScoreEntry:
|
||||
user_id: int
|
||||
username: str
|
||||
replay_id: int
|
||||
date: str
|
||||
beatmap_id: int
|
||||
beatmap_beatmapset_id: int
|
||||
beatmap_title: str
|
||||
beatmap_star_rating: float
|
||||
pp: float
|
||||
frametime: float
|
||||
ur: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class StolenReplayEntry:
|
||||
replay_id_1: int
|
||||
replay_id_2: int
|
||||
user_id_1: int
|
||||
user_id_2: int
|
||||
username_1: str
|
||||
username_2: str
|
||||
beatmap_beatmapset_id: int
|
||||
replay_date_1: str
|
||||
replay_date_2: str
|
||||
replay_pp_1: float
|
||||
replay_pp_2: float
|
||||
beatmap_id: int
|
||||
beatmap_title: str
|
||||
beatmap_star_rating: float
|
||||
similarity: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class BanListPagination:
|
||||
currentPage: int
|
||||
pageSize: int
|
||||
totalPages: int
|
||||
totalResults: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class BanListEntry:
|
||||
userId: int
|
||||
username: str
|
||||
secondsPlayed: int
|
||||
pp: float
|
||||
rank: int
|
||||
isBanned: bool
|
||||
approximateBanTime: datetime
|
||||
lastUpdate: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class BanList:
|
||||
pagination: BanListPagination
|
||||
users: List[BanListEntry]
|
||||
|
||||
|
||||
class NiseAPI:
|
||||
def __init__(self, base_url="https://nise.moe"):
|
||||
self.base_url = base_url
|
||||
self.headers = {
|
||||
"X-NISE-API": "20240218",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def search_scores(self, query) -> ScoreSearchResult:
|
||||
url = f"{self.base_url}/api/search"
|
||||
response = requests.post(url, headers=self.headers, data=json.dumps(query))
|
||||
return ScoreSearchResult(**response.json())
|
||||
|
||||
def search_users(self, query) -> UserSearchResult:
|
||||
url = f"{self.base_url}/api/search-user"
|
||||
response = requests.post(url, headers=self.headers, data=json.dumps(query))
|
||||
return UserSearchResult(**response.json())
|
||||
|
||||
def get_single_score(self, replay_id) -> ReplayData:
|
||||
url = f"{self.base_url}/api/score/{replay_id}"
|
||||
response = requests.get(url, headers=self.headers)
|
||||
try:
|
||||
return ReplayData(**response.json())
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError()
|
||||
|
||||
def get_user_details(self, user_id=None, username=None) -> UserProfile:
|
||||
url = f"{self.base_url}/api/user-details"
|
||||
data = {}
|
||||
if user_id:
|
||||
data["userId"] = user_id
|
||||
elif username:
|
||||
data["username"] = username
|
||||
response = requests.post(url, headers=self.headers, data=json.dumps(data))
|
||||
try:
|
||||
return UserProfile(**response.json())
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError()
|
||||
|
||||
def get_suspicious_scores(self) -> List[SuspiciousScoreEntry]:
|
||||
url = f"{self.base_url}/api/suspicious-scores"
|
||||
response = requests.get(url, headers=self.headers)
|
||||
return [SuspiciousScoreEntry(**item) for item in response.json()]
|
||||
|
||||
def get_similar_replays(self) -> List[StolenReplayEntry]:
|
||||
url = f"{self.base_url}/api/similar-replays"
|
||||
response = requests.get(url, headers=self.headers)
|
||||
return [StolenReplayEntry(**item) for item in response.json()]
|
||||
|
||||
def get_banlist(self, page) -> BanList:
|
||||
url = f"{self.base_url}/api/banlist"
|
||||
data = {"page": page}
|
||||
response = requests.post(url, headers=self.headers, data=json.dumps(data))
|
||||
return BanList(**response.json())
|
||||
94
nise-frontend/src/assets/nise_usage.py
Normal file
94
nise-frontend/src/assets/nise_usage.py
Normal file
@ -0,0 +1,94 @@
|
||||
nise_api = NiseAPI()
|
||||
|
||||
scores = nise_api.get_single_score(replay_id=3808640439)
|
||||
print(scores.adjusted_ur)
|
||||
|
||||
user = nise_api.get_user_details(username="degenerate")
|
||||
print(user)
|
||||
|
||||
suspicious_scores = nise_api.get_suspicious_scores()
|
||||
print(suspicious_scores)
|
||||
|
||||
stolen_replays = nise_api.get_similar_replays()
|
||||
print(stolen_replays)
|
||||
|
||||
banlist = nise_api.get_banlist(page=1)
|
||||
print(banlist)
|
||||
|
||||
score_query = {
|
||||
"queries": [
|
||||
{
|
||||
"predicates": [
|
||||
{
|
||||
"field": {
|
||||
"name": "user_rank",
|
||||
"type": "number"
|
||||
},
|
||||
"operator": {
|
||||
"operatorType": "<",
|
||||
"acceptsValues": "any"
|
||||
},
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"field": {
|
||||
"name": "ur",
|
||||
"type": "number"
|
||||
},
|
||||
"operator": {
|
||||
"operatorType": "<",
|
||||
"acceptsValues": "any"
|
||||
},
|
||||
"value": "120"
|
||||
}
|
||||
],
|
||||
"logicalOperator": "AND"
|
||||
}
|
||||
],
|
||||
"sorting": {
|
||||
"field": "user_id",
|
||||
"order": "ASC"
|
||||
},
|
||||
"page": 1
|
||||
}
|
||||
score_search = nise_api.search_scores(query=score_query)
|
||||
print(score_search)
|
||||
|
||||
user_query = {
|
||||
"queries": [
|
||||
{
|
||||
"predicates": [
|
||||
{
|
||||
"field": {
|
||||
"name": "rank",
|
||||
"type": "number"
|
||||
},
|
||||
"operator": {
|
||||
"operatorType": ">",
|
||||
"acceptsValues": "any"
|
||||
},
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"field": {
|
||||
"name": "username",
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"operatorType": "=",
|
||||
"acceptsValues": "any"
|
||||
},
|
||||
"value": "degenerate"
|
||||
}
|
||||
],
|
||||
"logicalOperator": "AND"
|
||||
}
|
||||
],
|
||||
"sorting": {
|
||||
"field": "user_id",
|
||||
"order": "ASC"
|
||||
},
|
||||
"page": 1
|
||||
}
|
||||
user_search = nise_api.search_users(query=user_query)
|
||||
print(user_search)
|
||||
Loading…
Reference in New Issue
Block a user