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;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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/'},
|
||||||
];
|
];
|
||||||
|
|||||||
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