diff --git a/lib/beatmap.ts b/lib/beatmap.ts index ab92556..659ea7c 100644 --- a/lib/beatmap.ts +++ b/lib/beatmap.ts @@ -1,4 +1,4 @@ -import { API, Beatmapset, Mod, RankStatus, Rulesets, Score, User } from "./index.js" +import { API, Beatmapset, Mod, Rulesets, Score, User } from "./index.js" import { getId } from "./misc.js" export interface Beatmap { @@ -58,7 +58,7 @@ export namespace Beatmap { mode_int: Rulesets passcount: number playcount: number - ranked: RankStatus + ranked: Beatmapset.RankStatus url: string } @@ -129,7 +129,7 @@ export namespace Beatmap { /** * Get an Array of up to 100 Beatmap.Packs of a specific type! - * @param type The type of the BeatmapPacks, defaults to "standard" + * @param type The type of the BeatmapPacks (defaults to **standard**) * @param cursor_string Use a response's `cursor_string` with the same parameters to get the next "page" of results! */ export async function getMultiple(this: API, type: "standard" | "featured" | "tournament" | "loved" | "chart" | "theme" | "artist" = "standard", diff --git a/lib/beatmapset.ts b/lib/beatmapset.ts index 56d9d50..26095f4 100644 --- a/lib/beatmapset.ts +++ b/lib/beatmapset.ts @@ -1,4 +1,4 @@ -import { API, Beatmap, Genres, Languages, RankStatus, Rulesets, User } from "./index.js" +import { API, Beatmap, Rulesets, User } from "./index.js" import { getId } from "./misc.js" export interface Beatmapset { @@ -14,7 +14,7 @@ export interface Beatmapset { slimcover: string "slimcover@2x": string } - creator: string + creator: User["username"] favourite_count: number id: number nsfw: boolean @@ -35,6 +35,51 @@ export interface Beatmapset { } export namespace Beatmapset { + export enum RankStatus { + Graveyard = -2, + Wip = -1, + Pending = 0, + Ranked = 1, + Approved = 2, + Qualified = 3, + Loved = 4 + } + + export enum Genres { + Any = 0, + Unspecified = 1, + "Video Game" = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, + "Hip Hop" = 9, + Electronic = 10, + Metal = 11, + Classical = 12, + Folk = 13, + Jazz = 14 + } + + export enum Languages { + Any = 0, + Unspecified = 1, + English = 2, + Japanese = 3, + Chinese = 4, + Instrumental = 5, + Korean = 6, + French = 7, + German = 8, + Swedish = 9, + Spanish = 10, + Italian = 11, + Russian = 12, + Polish = 13, + Other = 14 + } + /** Whether properties are there or not and null or not depend of the `type` */ export interface Event { id: number @@ -72,7 +117,7 @@ export namespace Beatmapset { * @param from Which beatmapset, or caused by which user? When? * @param types What kinds of events? * @param cursor_stuff How many results maximum to get, which page of those results, a cursor_string if you have that... - * @param sort (defaults to "id_desc") "id_asc" to have the oldest recent event first, "id_desc" to have the newest instead + * @param sort "id_asc" to have the oldest recent event first, "id_desc" to have the newest instead (defaults to **id_desc**) * @returns Relevant events and users * @remarks (2024-03-11) For months now, the API's documentation says the response is likely to change, so beware, * and also there's no documentation for this route in the API, so this is only the result of my interpretation of the website's code lol @@ -106,7 +151,6 @@ export namespace Beatmapset { } bpm: number can_be_hyped: boolean - creator: User["username"] deleted_at: Date | null discussion_locked: boolean is_scoreable: boolean @@ -118,7 +162,6 @@ export namespace Beatmapset { } ranked: RankStatus ranked_date: Date | null - source: string storyboard: boolean submitted_date: Date | null tags: string @@ -176,7 +219,7 @@ export namespace Beatmapset { deleted_by_id: User["id"] | null message_type: "suggestion" | "problem" | "mapper_note" | "praise" | "hype" | "review" /** For example, the id of the review this discussion is included in */ - parent_id: Discussion["id"] | null + parent_id: number | null timestamp: number | null resolved: boolean can_be_resolved: boolean @@ -241,7 +284,7 @@ export namespace Beatmapset { * @param from The discussion with the votes, the user who voted, the user who's gotten the votes... * @param score An upvote (1) or a downvote (-1) * @param cursor_stuff How many results maximum to get, which page of those results, a cursor_string if you have that... - * @param sort (defaults to "id_desc") "id_asc" to have the oldest recent vote first, "id_desc" to have the newest instead + * @param sort "id_asc" to have the oldest recent vote first, "id_desc" to have the newest instead (defaults to **id_desc**) * @returns Relevant votes and info about them * @remarks (2024-03-11) For months now, the API's documentation says the response is likely to change, so beware */ @@ -262,7 +305,7 @@ export namespace Beatmapset { * @param from From where/who are the discussions coming from? Maybe only qualified sets? * @param filter Should those discussions only be unresolved problems, for example? * @param cursor_stuff How many results maximum to get, which page of those results, a cursor_string if you have that... - * @param sort (defaults to "id_desc") "id_asc" to have the oldest recent discussion first, "id_desc" to have the newest instead + * @param sort "id_asc" to have the oldest recent discussion first, "id_desc" to have the newest instead (defaults to **id_desc**) * @returns Relevant discussions and info about them * @remarks (2024-03-11) For months now, the API's documentation says the response is likely to change, so beware * @privateRemarks I don't allow setting `beatmap_id` because my testing has led me to believe it does nothing (and is therefore misleading) @@ -297,7 +340,7 @@ export namespace Beatmapset { general?: ("Recommended difficulty" | "Include converted beatmaps" | "Subscribed mappers" | "Spotlighted beatmaps" | "Featured Artists")[], /** Only get sets that have maps that you can play in the ruleset of your choice */ mode?: Rulesets, - /** (defaults to all that have leaderboard) Filter in sets depending on their status or on their relation with the authorized user */ + /** Filter in sets depending on their status or on their relation with the authorized user (defaults to **all that have a leaderboard**) */ categories?: "Any" | "Ranked" | "Qualified" | "Loved" | "Favourites" | "Pending" | "WIP" | "Graveyard" | "My Maps", /** Use this to hide all sets that are marked as explicit */ hide_explicit_content?: true, diff --git a/lib/changelog.ts b/lib/changelog.ts index fed8c17..a668237 100644 --- a/lib/changelog.ts +++ b/lib/changelog.ts @@ -1,4 +1,4 @@ -import { API } from "./index.js" +import { API, User } from "./index.js" export namespace Changelog { export interface Build { @@ -41,8 +41,8 @@ export namespace Changelog { github_url: string | null github_username: string | null id: number | null - osu_username: string | null - user_id: number | null + osu_username: User["username"] | null + user_id: User["id"] | null user_url: string | null } /** diff --git a/lib/chat.ts b/lib/chat.ts index a7daf54..21ecac4 100644 --- a/lib/chat.ts +++ b/lib/chat.ts @@ -1,6 +1,5 @@ -import { API } from "./index.js"; +import { API, User } from "./index.js"; import { getId } from "./misc.js"; -import { User } from "./user.js"; export namespace Chat { /** @obtainableFrom {@link API.keepChatAlive} */ diff --git a/lib/event.ts b/lib/event.ts index 934a3ae..188c51e 100644 --- a/lib/event.ts +++ b/lib/event.ts @@ -1,5 +1,4 @@ -import { API } from "./index.js" -import { Rulesets } from "./misc.js" +import { API, User as UserImport, Rulesets } from "./index.js" export interface Event { created_at: Date @@ -10,20 +9,20 @@ export namespace Event { /** Those are used as properties by Events, they're not events themselves */ export namespace SharedProperties { export interface User { - username: string + username: UserImport["username"] /** What goes after the website's URL, so for example, it could be the `/u/7276846` of `https://osu.ppy.sh/u/7276846` (or `users` instead of `u`) */ url: string } export interface Beatmap { - /** {artist} - {title} [{difficulty_name}] */ + /** Format: {artist} - {song_name} [{difficulty_name}] */ title: string /** What goes after the website's URL, like it could be the `/b/2980857?m=0` of `https://osu.ppy.sh/b/2980857?m=0` (/{beatmap_id}?m={ruleset_id}) */ url: string } export interface Beatmapset { - /** {artist} - {title} */ + /** Format: {artist} - {song_name} */ title: string /** What goes after the website's URL, like it could be the `/s/689155` of `https://osu.ppy.sh/s/689155` (/{beatmapset_id}) */ url: string @@ -121,10 +120,10 @@ export namespace Event { export interface UsernameChange extends Event { type: "usernameChange" user: { - username: string + username: UserImport["username"] /** What goes after the website's URL, so for example, it could be the `/u/7276846` of `https://osu.ppy.sh/u/7276846` (or `users` instead of `u`) */ url: string - previousUsername: string + previousUsername: UserImport["username"] } } @@ -135,7 +134,7 @@ export namespace Event { /** * Get everything note-worthy that happened on osu! recently! - * @param sort (defaults to "id_desc") "id_asc" to have the oldest recent event first, "id_desc" to have the newest instead + * @param sort "id_asc" to have the oldest recent event first, "id_desc" to have the newest instead (defaults to **id_desc**) * @param cursor_string Use a response's `cursor_string` with the same parameters to get the next "page" of results, so `posts` in this instance! */ export async function getMultiple(this: API, sort: "id_desc" | "id_asc" = "id_desc", cursor_string?: string): diff --git a/lib/forum.ts b/lib/forum.ts index 41b8eab..16ae00e 100644 --- a/lib/forum.ts +++ b/lib/forum.ts @@ -136,8 +136,8 @@ export namespace Forum { * Get a forum topic, as well as its main post (content) and the posts that were sent in it! * @remarks The oldest post of a topic is the text of a topic * @param topic An object with the id of the topic in question - * @param limit (defaults to 20, max 50) How many `posts` maximum? - * @param sort (defaults to "id_asc") "id_asc" to have the oldest post at the beginning of the `posts` array, "id_desc" to have the newest instead + * @param limit How many `posts` maximum, up to 50 (defaults to **20**) + * @param sort "id_asc" to have the oldest post at the beginning of the `posts` array, "id_desc" to have the newest instead (defaults to **id_asc**) * @param first_post (ignored if `cursor_string`) An Object with the id of the first post to be returned in `posts` * @param cursor_string Use a response's `cursor_string` with the same parameters to get the next "page" of results, so `posts` in this instance! */ diff --git a/lib/home.ts b/lib/home.ts index 6eae81a..2578e46 100644 --- a/lib/home.ts +++ b/lib/home.ts @@ -1,6 +1,4 @@ -import { API } from "./index.js" -import { User } from "./user.js" -import { WikiPage } from "./wiki.js" +import { API, User, WikiPage } from "./index.js" export namespace Home { export namespace Search { diff --git a/lib/index.ts b/lib/index.ts index f0a7445..5889a37 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -34,7 +34,7 @@ export { Forum, PollConfig } from "./forum.js" export { WikiPage } from "./wiki.js" export { NewsPost } from "./news.js" export { Home } from "./home.js" -export { Rulesets, Mod, Scope, Genres, Languages, RankStatus, Spotlight } from "./misc.js" +export { Rulesets, Mod, Scope, Spotlight } from "./misc.js" export { Chat } from "./chat.js" export { WebSocket } from "./websocket.js" export { Comment } from "./comment.js" diff --git a/lib/misc.ts b/lib/misc.ts index 8382cb6..41c0a56 100644 --- a/lib/misc.ts +++ b/lib/misc.ts @@ -3,7 +3,8 @@ import { API } from "./index.js" /** * Scopes determine what the API instance can do as a user! * https://osu.ppy.sh/docs/index.html#scopes - * @remarks "identify" is always implicity provided, **"public" is implicitly needed for almost everything** + * @remarks "identify" is always implicity provided, **"public" is implicitly needed for almost everything!!** + * The need for the "public" scope is only made explicit when the function can't be used unless the application acts as as a user (non-guest) */ export type Scope = "chat.read" | "chat.write" | "chat.write_manage" | "delegate" | "forum.write" | "friends.read" | "identify" | "public" @@ -19,51 +20,6 @@ export enum Rulesets { mania = 3 } -export enum RankStatus { - Graveyard = -2, - Wip = -1, - Pending = 0, - Ranked = 1, - Approved = 2, - Qualified = 3, - Loved = 4 -} - -export enum Genres { - Any = 0, - Unspecified = 1, - "Video Game" = 2, - Anime = 3, - Rock = 4, - Pop = 5, - Other = 6, - Novelty = 7, - "Hip Hop" = 9, - Electronic = 10, - Metal = 11, - Classical = 12, - Folk = 13, - Jazz = 14, -} - -export enum Languages { - Any = 0, - Unspecified = 1, - English = 2, - Japanese = 3, - Chinese = 4, - Instrumental = 5, - Korean = 6, - French = 7, - German = 8, - Swedish = 9, - Spanish = 10, - Italian = 11, - Russian = 12, - Polish = 13, - Other = 14, -} - /** @obtainableFrom {@link API.getSpotlights} */ export interface Spotlight { id: number @@ -83,11 +39,11 @@ export namespace Spotlight { /** * Get ALL legacy spotlights! (2009-2020, somewhat known as charts/ranking charts, available @ https://osu.ppy.sh/rankings/osu/charts) * @remarks The data for newer spotlights (2020-, somewhat known as seasons) can be obtained through `getRoom()` - * but you can't really get their id without going through the website's URLs (https://osu.ppy.sh/seasons/latest) as far as I know :( + * but you can't really get the id of those newer spotlights without going through the website's URLs (https://osu.ppy.sh/seasons/latest) as far as I know :( */ export async function getAll(this: API): Promise { const response = await this.request("get", "spotlights") - return response.spotlights + return response.spotlights // It's the only property } } diff --git a/lib/multiplayer.ts b/lib/multiplayer.ts index 8d1480d..8527db9 100644 --- a/lib/multiplayer.ts +++ b/lib/multiplayer.ts @@ -45,7 +45,7 @@ export namespace Multiplayer { export namespace Room { export interface PlaylistItem { id: number - room_id: number // `Room["id"]` messes up tsj/ajv somehow in tests + room_id: number beatmap_id: Beatmap["id"] ruleset_id: Rulesets allowed_mods: Mod[] diff --git a/lib/news.ts b/lib/news.ts index 8be8c85..1a5b2f9 100644 --- a/lib/news.ts +++ b/lib/news.ts @@ -1,9 +1,9 @@ -import { API } from "./index.js" +import { API, User } from "./index.js" /** @obtainableFrom {@link API.getNewsPosts} */ export interface NewsPost { id: number - author: string + author: User["username"] /** Link to view the file on GitHub */ edit_url: string /** Link to the first image in the document */ @@ -28,21 +28,23 @@ export namespace NewsPost { /** * Get a NewsPost, its content, and the NewsPosts right before and right after it! - * @param post An object with the id or the slug of a NewsPost (the slug being the filename minus the extension, used in its URL) + * @param post The NewsPost, or its id, or its slug */ - export async function getOne(this: API, post: {id?: number, slug?: string} | NewsPost): Promise { - const key = post.id !== undefined ? "id" : undefined - const lookup = post.id !== undefined ? post.id : post.slug + export async function getOne(this: API, post: NewsPost["id"] | NewsPost["slug"] | NewsPost): Promise { + const lookup = typeof post === "object" ? post.id : post + const key = typeof post === "string" ? undefined : "id" return await this.request("get", `news/${lookup}`, {key}) } /** * Get all the NewsPosts of a specific year! * @remarks If the specified year is invalid/has no news, it fallbacks to the default year - * @param year (defaults to current year) The year the posts were made + * @param year The year the posts were made (defaults to **current year**) + * @privateRemarks Because the only filter is the year, everything but `news_sidebar.news_posts` is actually completely useless! + * You could maybe make a case for `years` being useful, but I don't believe it's useful enough to sacrifice the simplicity */ export async function getMultiple(this: API, year?: number): Promise { - const response = await this.request("get", "news", {year, limit: 1}) - return response.news_sidebar.news_posts + const response = await this.request("get", "news", {year, limit: 0}) // Put the limit at minimum because it's about stuff we're filtering out anyway + return response.news_sidebar.news_posts // NOT the only property as explained by the private remarks; it's believed to be the only USEFUL property } } diff --git a/lib/ranking.ts b/lib/ranking.ts index f59c064..0cfcbb8 100644 --- a/lib/ranking.ts +++ b/lib/ranking.ts @@ -46,8 +46,8 @@ export namespace Ranking { * Get the top players of the game, with some filters! * @param ruleset Self-explanatory, is also known as "Gamemode" * @param type Rank players by their performance points or by their ranked score? - * @param page (defaults to 1) Imagine `Rankings` as a page, it can only have a maximum of 50 players, while 50 others may be on the next one - * @param filter What kind of players do you want to see? Defaults to `all`, `friends` has no effect if no authorized user + * @param page Imagine the array you get as a page, it can only have a maximum of 50 players, while 50 others may be on the next one (defaults to **1**) + * @param filter What kind of players do you want to see? Keep in mind `friends` has no effect if no authorized user (defaults to **all**) * @param country Only get players from a specific country, using its ISO 3166-1 alpha-2 country code! (France would be `FR`, United States `US`) * @param variant If `type` is `performance` and `ruleset` is mania, choose between 4k and 7k! */ @@ -59,15 +59,13 @@ export namespace Ranking { /** * Get the top countries of a specific ruleset! * @param ruleset On which Ruleset should the countries be compared? - * @param page (defaults to 1) Imagine `Rankings` as a page, it can only have a maximum of 50 countries, while 50 others may be on the next one + * @param page Imagine the array you get as a page, it can only have a maximum of 50 countries, while 50 others may be on the next one (defaults to **1**) */ export async function getCountry(this: API, ruleset: Rulesets, page: number = 1): Promise { return await this.request("get", `rankings/${Rulesets[ruleset]}/country`, {page}) } - /** - * Get the top 50 players who have the most total kudosu! - */ + /** Get the top 50 players who have the most total kudosu! */ export async function getKudosu(this: API): Promise { const response = await this.request("get", "rankings/kudosu") return response.ranking @@ -77,7 +75,7 @@ export namespace Ranking { * Get the rankings of a spotlight from 2009 to 2020 on a specific ruleset! * @param ruleset Each spotlight has a different ranking (and often maps) depending on the ruleset * @param spotlight The spotlight in question - * @param filter What kind of players do you want to see? Defaults to `all`, `friends` has no effect if no authorized user + * @param filter What kind of players do you want to see? Keep in mind `friends` has no effect if no authorized user (defaults to **all**) */ export async function getSpotlight(this: API, ruleset: Rulesets, spotlight: SpotlightInterface["id"] | SpotlightInterface, filter: "all" | "friends" = "all"): Promise { return await this.request("get", `rankings/${Rulesets[ruleset]}/charts`, {spotlight: getId(spotlight), filter}) diff --git a/lib/score.ts b/lib/score.ts index a7a9c98..0e306d0 100644 --- a/lib/score.ts +++ b/lib/score.ts @@ -1,8 +1,5 @@ -import { Beatmap } from "./beatmap.js" -import { Beatmapset } from "./beatmapset.js" -import { API } from "./index.js" -import { Mod, Rulesets, getId } from "./misc.js" -import { User } from "./user.js" +import { API, Beatmap, Beatmapset, Changelog, Mod, Multiplayer as MultiplayerImport, Rulesets, 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) */ @@ -52,12 +49,12 @@ export namespace Score { ended_at: Date maximum_statistics: Statistics mods: Mod[] - ruleset_id: number + ruleset_id: Rulesets started_at: Date statistics: Statistics total_score: number - playlist_item_id: number - room_id: number + playlist_item_id: MultiplayerImport.Room.PlaylistItem["id"] + room_id: MultiplayerImport.Room["id"] id: number user: User.WithCountryCover } @@ -71,9 +68,9 @@ export namespace Score { preserve: boolean mods: Mod[] statistics: Statistics - beatmap_id: number + beatmap_id: Beatmap["id"] /** @remarks Is null if the score has not been set on lazer */ - build_id: number | null + build_id: Changelog.Build["id"] | null ended_at: Date has_replay: boolean is_perfect_combo: boolean diff --git a/lib/tests/test.ts b/lib/tests/test.ts index 183e9ce..5ac14e9 100644 --- a/lib/tests/test.ts +++ b/lib/tests/test.ts @@ -7,6 +7,8 @@ import * as osu from "../index.js" import "dotenv/config" import util from "util" +// Because of the way tests are made, some things in the package are basic types instead of referring to other properties +// A basic example would be WikiPage's available_locales, which could've been WikiPage["locale"] import tsj from "ts-json-schema-generator" import ajv from "ajv" @@ -251,7 +253,7 @@ const testNews = async (): Promise => { let okay = true console.log("\n===> NEWS") - const a = await attempt(api.getNewsPost, {id: 26}) + const a = await attempt(api.getNewsPost, 26) if (!isOk(a, !a || (a.title === "Official osu! Fanart Contest 5 Begins!" && validate(a, "NewsPost.WithContentNavigation")))) okay = false const b = await attempt(api.getNewsPosts) if (!isOk(b, !b || (b.length >= 1 && validate(b, "NewsPost")))) okay = false diff --git a/lib/websocket.ts b/lib/websocket.ts index 995ee5e..8c82a54 100644 --- a/lib/websocket.ts +++ b/lib/websocket.ts @@ -1,5 +1,4 @@ -import { Chat } from "./chat.js" -import { User } from "./user.js" +import { Chat, User } from "./index.js" /** Everything here is great to use with the WebSocket you can get with {@link API.generateWebSocket}! */ export namespace WebSocket { diff --git a/lib/wiki.ts b/lib/wiki.ts index a0dda13..8708843 100644 --- a/lib/wiki.ts +++ b/lib/wiki.ts @@ -1,12 +1,10 @@ import { API } from "./index.js" -/** - * Expected from api.getWikiPage(), SearchResultWiki - */ +/** @obtainableFrom {@link API.getWikiPage} */ export interface WikiPage { available_locales: string[] layout: string - /** BCP 47 language (sub)tag, lowercase (for example, `en` for english) */ + /** Lowercase BCP 47 language (sub)tag (for example, `en` for english) */ locale: string markdown: string /** It's what should be after `https://osu.ppy.sh/wiki/{locale}/` */ @@ -25,9 +23,9 @@ 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 (defaults to "en") The BCP 47 language (sub)tag, lowercase (for example, for the article in french, use "fr") + * @param locale The BCP 47 language (sub)tag lowercase (for example, for a french WikiPage, use "fr") (defaults to **en**) */ - export async function getOne(this: API, path: string, locale: string = "en"): Promise { + export async function getOne(this: API, path: string, locale: WikiPage["locale"] = "en"): Promise { return await this.request("get", `wiki/${locale}/${path}`) } }