+
+ NEW! check out the python api
+ wrapper
+
+
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. currently there's no rate limits.
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.
diff --git a/nise-frontend/src/app/app-routing.module.ts b/nise-frontend/src/app/app-routing.module.ts
index 6b8a4ce..474969e 100644
--- a/nise-frontend/src/app/app-routing.module.ts
+++ b/nise-frontend/src/app/app-routing.module.ts
@@ -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/'},
];
diff --git a/nise-frontend/src/assets/nise.py b/nise-frontend/src/assets/nise.py
new file mode 100644
index 0000000..b6136f0
--- /dev/null
+++ b/nise-frontend/src/assets/nise.py
@@ -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())
diff --git a/nise-frontend/src/assets/nise_usage.py b/nise-frontend/src/assets/nise_usage.py
new file mode 100644
index 0000000..1c14129
--- /dev/null
+++ b/nise-frontend/src/assets/nise_usage.py
@@ -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)