py wrapper

This commit is contained in:
nise.moe 2024-06-12 14:17:19 +02:00
parent 64f3960a1d
commit e279c4a63f
8 changed files with 519 additions and 0 deletions

View 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>

View 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);
}
}

View File

@ -2,3 +2,11 @@ section {
padding-bottom: 30px; padding-bottom: 30px;
border-bottom: 2px dotted rgba(224, 224, 224, 0.32); 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);
}

View File

@ -1,9 +1,17 @@
<div class="main term mb-2"> <div class="main term mb-2">
<div class="fade-stuff"> <div class="fade-stuff">
<h1 class="mb-4"># <span class="board">/api/</span> stuff</h1> <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> <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> 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> </p>
<div class="alert mb-2 text-center"> <div class="alert mb-2 text-center">
<p> <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. 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.

View File

@ -11,6 +11,7 @@ import {ContributeComponent} from "./contribute/contribute.component";
import {BanlistComponent} from "./banlist/banlist.component"; import {BanlistComponent} from "./banlist/banlist.component";
import {ProfileComponent} from "./profile/profile.component"; import {ProfileComponent} from "./profile/profile.component";
import {ApiComponent} from "./api/api.component"; import {ApiComponent} from "./api/api.component";
import {ApiPythonComponent} from "./api-python/api-python.component";
const routes: Routes = [ const routes: Routes = [
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'}, {path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
@ -33,6 +34,7 @@ const routes: Routes = [
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'}, {path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
{path: 'docs', component: ApiComponent, title: '/api/ explanation'}, {path: 'docs', component: ApiComponent, title: '/api/ explanation'},
{path: 'docs/py', component: ApiPythonComponent, title: 'python lib'},
{path: '**', component: HomeComponent, title: '/nise.moe/'}, {path: '**', component: HomeComponent, title: '/nise.moe/'},
]; ];

View 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())

View 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)