Skip to content

Commit

Permalink
Make use of the x-api-version header
Browse files Browse the repository at this point in the history
  • Loading branch information
TTTaevas committed Oct 27, 2024
1 parent fc15d32 commit 2851cb0
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 147 deletions.
50 changes: 22 additions & 28 deletions lib/Beatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,40 +234,34 @@ export namespace Beatmap {
}
}

/** @obtainableFrom {@link API.getBeatmapUserScore} */
export interface UserScore {
/** Value depends on the requested mode and mods! */
position: number
/** The score itself */
score: Score.WithUserBeatmap
}

export namespace UserScore {
/**
/**
* Get the score on a beatmap made by a specific user (with specific mods and on a specific ruleset if needed)
* @param beatmap The Beatmap the score was made on
* @param user The User who made the score
* @param config Specify the score's ruleset, the score's mods, prevent a lazer score from being returned **(`type` should not be supported)**
* @returns An Object with the position of the score according to the specified Mods and Ruleset, and with the score itself
*/
export async function getOne(this: API, beatmap: Beatmap["id"] | Beatmap, user: User["id"] | User, config?: Config): Promise<UserScore> {
const mode = config?.ruleset !== undefined ? Ruleset[config.ruleset] : undefined
return await this.request("get", `beatmaps/${getId(beatmap)}/scores/users/${getId(user)}`,
{legacy_only: config?.legacy_only, mode, mods: config?.mods, type: config?.type})
}
export async function getUserScore(this: API, beatmap: Beatmap["id"] | Beatmap, user: User["id"] | User, config?: Config): Promise<{
/** Value depends on the requested mode and mods! */
position: number,
score: Score.WithUserBeatmap
}> {
const mode = config?.ruleset !== undefined ? Ruleset[config.ruleset] : undefined
return await this.request("get", `beatmaps/${getId(beatmap)}/scores/users/${getId(user)}`,
{legacy_only: config?.legacy_only, mode, mods: config?.mods, type: config?.type})
}

/**
* Get the scores on a beatmap made by a specific user (with the possibility to specify if the scores are on a convert)
* @param beatmap The Beatmap the scores were made on
* @param user The User who made the scores
* @param config Specify the score's ruleset, prevent a lazer score from being returned **(`mods` and `type` should not be supported)**
*/
export async function getMultiple(this: API, beatmap: Beatmap["id"] | Beatmap, user: User["id"] | User, config?: Config): Promise<Score.Legacy[]> {
const mode = config?.ruleset !== undefined ? Ruleset[config.ruleset] : undefined
const response = await this.request("get", `beatmaps/${getId(beatmap)}/scores/users/${getId(user)}/all`,
{legacy_only: config?.legacy_only, mode, mods: config?.mods, type: config?.type})
return response.scores // It's the only property
}
/**
* Get the scores on a beatmap made by a specific user (with the possibility to specify if the scores are on a convert)
* @param beatmap The Beatmap the scores were made on
* @param user The User who made the scores
* @param config Specify the score's ruleset, prevent a lazer score from being returned **(`mods` and `type` should not be supported)**
*/
export async function getUserScores(this: API, beatmap: Beatmap["id"] | Beatmap, user: User["id"] | User, config?: Config): Promise<Score[]> {
const mode = config?.ruleset !== undefined ? Ruleset[config.ruleset] : undefined
const response = await this.request("get", `beatmaps/${getId(beatmap)}/scores/users/${getId(user)}/all`,
{legacy_only: config?.legacy_only, mode, mods: config?.mods, type: config?.type})
return response.scores // It's the only property
}

/**
Expand Down Expand Up @@ -317,7 +311,7 @@ export namespace Beatmap {
* @param config Specify the score's ruleset, mods, type **(`legacy_only` should not be supported)**
* @remarks Please check if `mods` and `type` seem to be supported or not by the API: https://osu.ppy.sh/docs/index.html#get-beatmap-scores-non-legacy
*/
export async function getSoloScores(this: API, beatmap: Beatmap["id"] | Beatmap, config?: Config): Promise<Score.Solo[]> {
export async function getSoloScores(this: API, beatmap: Beatmap["id"] | Beatmap, config?: Config): Promise<Score.WithUser[]> {
const mode = config?.ruleset !== undefined ? Ruleset[config.ruleset] : undefined
const response = await this.request("get", `beatmaps/${getId(beatmap)}/solo-scores`,
{legacy_only: config?.legacy_only, mode, mods: config?.mods, type: config?.type})
Expand Down
40 changes: 24 additions & 16 deletions lib/Multiplayer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { API, Beatmap, Chat, Mod, Ruleset, Score, User } from "./index.js"
import { API, Beatmap, Chat, Mod, Ruleset, Score as ScoreImport, User } from "./index.js"
import { getId } from "./misc.js"

export namespace Multiplayer {
Expand Down Expand Up @@ -57,19 +57,9 @@ export namespace Multiplayer {
}

export namespace PlaylistItem {
/** @obtainableFrom {@link API.getPlaylistItemScores} */
export interface Scores {
params: {
limit: number
sort: string
}
scores: Score.Multiplayer[]
/** How many scores there are across all pages, not necessarily `scores.length` */
total: number
/** @remarks Will be null if not an authorized user or if the authorized user has no score */
user_score: Score.Multiplayer | null
/** @remarks Will be null if there is no next page */
cursor_string: string | null
export interface Score extends ScoreImport.WithUser {
playlist_item_id: PlaylistItem["id"]
room_id: Room["id"]
}

/**
Expand All @@ -82,7 +72,16 @@ export namespace Multiplayer {
* https://github.com/ppy/osu-web/issues/10725
*/
export async function getScores(this: API, item: {id: number, room_id: number} | Multiplayer.Room.PlaylistItem, limit: number = 50,
sort: "score_asc" | "score_desc" = "score_desc", cursor_string?: string): Promise<Multiplayer.Room.PlaylistItem.Scores> {
sort: "score_asc" | "score_desc" = "score_desc", cursor_string?: string): Promise<{
params: {limit: number, sort: string}
scores: Score[]
/** How many scores there are across all pages, not necessarily `scores.length` */
total: number
/** @remarks Will be null if not an authorized user or if the authorized user has no score */
user_score: Score | null
/** @remarks Will be null if there is no next page */
cursor_string: string | null
}> {
return await this.request("get", `rooms/${item.room_id}/playlist/${item.id}/scores`, {limit, sort, cursor_string})
}
}
Expand Down Expand Up @@ -150,6 +149,15 @@ export namespace Multiplayer {
}

export namespace Match {
export interface Score extends ScoreImport.OldFormat {
created_at: Date
match: {
slot: number
team: "none" | "red" | "blue"
pass: boolean
}
}

export interface Event {
id: number
detail: {
Expand All @@ -171,7 +179,7 @@ export namespace Multiplayer {
team_type: string
mods: string[]
beatmap: Beatmap.WithBeatmapset
scores: Score.WithMatch[]
scores: Score[]
}
}

Expand Down
131 changes: 51 additions & 80 deletions lib/Score.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import { API, Beatmap, Beatmapset, Changelog, Mod, Multiplayer as MultiplayerImport, Ruleset, User } from "./index.js"
import { API, Beatmap, Beatmapset, Changelog, Mod, Ruleset, User } from "./index.js"
import { getId } from "./misc.js"

interface Bare {
/** In a format where `96.40%` would be `0.9640` (likely with some numbers after the zero) */
/** Common to older and newer formats */
interface Basic {
/** In a format where `96.40%` would be `0.9640` **(and maybe some numbers afterwards)** */
accuracy: number
best_id: number | null
/** Would be null if ScoreV2 on stable, for example */
id: number | null
max_combo: number
mods: Mod[] | string[]
passed: boolean
/** Also known as a grade, for example this is `X` (SS) if `accuracy` is `1` (100.00%) */
rank: string
/** The ID of the user who made the score */
user_id: User["id"]
/** @remarks Is null when Beatmap is Loved (for example) */
pp: number | null
/** Can this score's replay be downloaded from the website? */
replay: boolean
/** Score format */
type: string
}

export interface Score extends Bare {
best_id: number | null
/** @remarks Is null when Beatmap is Loved (for example) */
pp: number | null
export interface Score extends Basic {
classic_total_score: number
preserve: boolean
ranked: boolean
maximum_statistics: Score.Statistics
mods: Mod[]
statistics: Score.Statistics
beatmap_id: Beatmap["id"]
id: number
/** @remarks Is null if the score has not been set on lazer */
build_id: Changelog.Build["id"] | null
ended_at: Date
has_replay: boolean
is_perfect_combo: boolean
legacy_perfect: boolean
/** @remarks Is null if the score has been set on lazer */
legacy_score_id: number | null
legacy_total_score: number
ruleset_id: Ruleset
started_at: Date | null
total_score: number
type: "solo_score"
current_user_attributes: {
pin: boolean | null
}
}

export namespace Score {
Expand All @@ -38,62 +62,37 @@ export namespace Score {
small_tick_miss?: number
large_bonus?: number
small_bonus?: number
/** Exclusively for the `maximum_statistics` of solo-scores that were not set on lazer */
legacy_combo_increase?: number
}

export interface Multiplayer extends Bare {
/** In a format where `96.40%` would be `0.9640` **(and no number afterwards)** */
accuracy: number
beatmap_id: Beatmap["id"]
ended_at: Date
maximum_statistics: Statistics
mods: Mod[]
ruleset_id: Ruleset
started_at: Date
statistics: Statistics
total_score: number
playlist_item_id: MultiplayerImport.Room.PlaylistItem["id"]
room_id: MultiplayerImport.Room["id"]
id: number
/** @obtainableFrom {@link API.getUserScores} */
export interface WithUserBeatmapBeatmapset extends Score {
beatmap: Beatmap.Extended
beatmapset: Beatmapset
user: User
/** @remarks Only if `type` is set to `best` on {@link API.getUserScores} */
weight?: {
percentage: number
pp: number
}
}

export interface WithUser extends Score {
user: User.WithCountryCover
}

/**
* Scores called "solo-scores" are more relevant to lazer stuff, it's the opposite of legacy
* @obtainableFrom {@link API.getBeatmapSoloScores}
*/
export interface Solo extends Score {
ranked: boolean
preserve: boolean
mods: Mod[]
statistics: Statistics
beatmap_id: Beatmap["id"]
/** @remarks Is null if the score has not been set on lazer */
build_id: Changelog.Build["id"] | null
ended_at: Date
has_replay: boolean
is_perfect_combo: boolean
legacy_perfect: boolean
legacy_score_id: number | null
legacy_total_score: number
started_at: Date | null
total_score: number
export interface WithUserBeatmap extends WithUser {
user: User.WithCountryCover
maximum_statistics?: Statistics
beatmap: Beatmap.Extended
}

/**
* The version of Score without lazer-related stuff, used almost everywhere!
* @obtainableFrom {@link API.getBeatmapUserScores}
*/
export interface Legacy extends Score {
/** The old version of scores, barely still used */
export interface OldFormat extends Basic {
mode: keyof typeof Ruleset
mode_int: Ruleset
mods: string[]
score: number
perfect: boolean
created_at: Date
statistics: {
/** @remarks Is null if the score's gamemode is Taiko */
count_50: number | null
Expand All @@ -105,41 +104,13 @@ export namespace Score {
}
}

export interface WithMatch extends Legacy {
match: {
slot: number
team: string
pass: boolean
}
}

/** @obtainableFrom {@link API.getBeatmapScores} */
export interface WithUser extends Legacy {
user: User.WithCountryCover
}

export interface WithUserBeatmap extends Legacy {
user: User
beatmap: Beatmap.Extended
}

/** @obtainableFrom {@link API.getUserScores} */
export interface WithUserBeatmapBeatmapset extends WithUserBeatmap {
beatmapset: Beatmapset
/** @remarks Only if `type` is set to `best` on {@link API.getUserScores} */
weight?: {
percentage: number
pp: number
}
}

/**
* Get the replay for a score!
* @scope {@link Scope"public"}
* @param score The score that has created the replay
* @returns The correctly encoded content of what would be a replay file (you can just fs.writeFileSync with it!)
*/
export async function getReplay(this: API, score: Exclude<Score["id"], null> | Score): Promise<string> {
export async function getReplay(this: API, score: Score["id"] | Score): Promise<string> {
return await this.request("get", `scores/${getId(score)}/download`)
}
}
15 changes: 9 additions & 6 deletions lib/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,20 @@ export namespace User {
}

/** @obtainableFrom {@link API.getFriends} */
export interface Friend {
target_id: User["id"]
relation_type: "friend" | "block"
mutual: boolean
target: WithCountryCoverGroupsStatisticsSupport
}

export interface WithCountryCoverGroupsStatisticsSupport extends WithCountryCover, WithGroups {
statistics: Statistics
support_level: number
}

/** @obtainableFrom {@link API.getUser} */
export interface Extended extends WithCountryCoverGroupsStatisticsSupport, WithKudosu {
/**
* @deprecated Please use `cover` instead!
* @privateRemarks When there's no cover, like on the dev server, this is null; keeping it non-null for convenience
*/
cover_url: string
discord: string | null
has_supported: boolean
interests: string | null
Expand Down Expand Up @@ -367,8 +369,9 @@ export namespace User {
/**
* Get user data of each friend of the authorized user
* @scope {@link Scope"friends.read"}
* @remarks The Statistics will be of the authorized user's favourite gamemode, not the friend's!
*/
export async function getFriends(this: API): Promise<User.WithCountryCoverGroupsStatisticsSupport[]> {
export async function getFriends(this: API): Promise<User.Friend[]> {
return await this.request("get", "friends")
}
}
4 changes: 2 additions & 2 deletions lib/WikiPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { API } from "./index.js"
export interface WikiPage {
available_locales: string[]
layout: string
/** Lowercase BCP 47 language (sub)tag (for example, `en` for english) */
/** Lowercase language tag ("fr" for french, "pt-br" for brazilian portuguese) */
locale: string
markdown: string
/** It's what should be after `https://osu.ppy.sh/wiki/{locale}/` */
Expand All @@ -23,7 +23,7 @@ export namespace WikiPage {
* Get a wiki page!
* @param path What's in the page's URL after `https://osu.ppy.sh/wiki/` (so the title, after the subtitle if there is a subtitle)
* (An example for `https://osu.ppy.sh/wiki/en/Game_mode/osu!` would be `Game_mode/osu!`)
* @param locale The BCP 47 language (sub)tag lowercase (for example, for a french WikiPage, use "fr") (defaults to **en**)
* @param locale Lowercase language tag ("fr" for french, "pt-br" for brazilian portuguese) (defaults to **en**)
*/
export async function getOne(this: API, path: string, locale: WikiPage["locale"] = "en"): Promise<WikiPage> {
return await this.request("get", `wiki/${locale}/${path}`)
Expand Down
Loading

0 comments on commit 2851cb0

Please sign in to comment.