Skip to content

TTTaevas/osu-api-v2-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

osu-api-v2-js

osu-api-v2-js is a JavaScript & TypeScript package that helps you interact with osu!api (v2).

The documentation for the latest version of this package can be found at any time on osu-v2.taevas.xyz!

How to install and get started

Before installing, if using Node.js, check if you're running version 20 or above:

node -v # displays your version of node.js

Then to install the package, use a command from your package manager:

npm i osu-api-v2-js # if using npm
yarn add osu-api-v2-js # if using yarn
pnpm add osu-api-v2-js # if using pnpm
bun a osu-api-v2-js # if using bun

You will want to create your own OAuth application: https://osu.ppy.sh/home/account/edit#oauth To use (import) the package in your project and start interacting with the API, you may do something like that:

// TypeScript
import * as osu from "osu-api-v2-js"

async function logUserTopPlayBeatmap(username: string) {
    // It's more convenient to use `osu.API.createAsync()` instead of `new osu.API()` as it doesn't require you to directly provide an access_token!
    // In a proper application, you'd use this function as soon as the app starts so you can use that object everywhere
    // (or if it acts as a user, you'd use this function at the end of the authorization flow)
    const api = await osu.API.createAsync("<client_id>", "<client_secret>") // with id as a number

    const user = await api.getUser(username) // We need to get the id of the user in order to request what we want
    const score = (await api.getUserScores(user, "best", osu.Ruleset.osu, {lazer: false}, {limit: 1}))[0] // Specifying the Ruleset is optional
    const beatmapDifficulty = await api.getBeatmapDifficultyAttributesOsu(score.beatmap, score.mods) // Specifying the mods so the SR is adapted to them

    const x = `${score.beatmapset.artist} - ${score.beatmapset.title} [${score.beatmap.version}]`
    const y = `+${score.mods.map((m) => m.acronym).toString()} (${beatmapDifficulty.star_rating.toFixed(2)}*)`
    console.log(`${username}'s top play is on: ${x} ${y}`)
    // Doomsday fanboy's top play is on: Erio o Kamattechan - os-Uchuujin(Asterisk Makina Remix) [Mattress Actress] +DT,CL (8.87*)
}

logUserTopPlayBeatmap("Doomsday fanboy")

Authorization flow

A simple guide on how to do extra fancy stuff

Create and share the authorization link with your users

If your application is meant to act on behalf of a user after they've clicked on a button to say they "consent to your application identifying them and reading public data on their behalf and some other stuff maybe", then you will need to take a slightly different approach to create your api object.

Let's take it step by step! First, this package comes with generateAuthorizationURL(), which will create a link for you that you can share so users can click on it and allow your application to do stuff on their behalf.

This function requires you to specify scopes. Here are some useful things to know about scopes:

  • identify is always implicitly SPECIFIED, so you should always use it
  • public is always implicitely REQUIRED, so you should (basically) always use it
  • when a method requires another scope, it will be explicit about it in its documentation
  • ultimately, the user is the one who decides which scopes they allow, so code assuming they may have allowed no scope at all
  • scopes that have been allowed can be checked at any time through the scopes property of your api object

Have a server ready to receive the authorization

The user clicked your link and authorized your application! ...Now what?

When a user authorizes your application, they get redirected to your Application Callback URL with a huge code as a GET parameter (the name of the parameter is code), and it is this very code that will allow you to proceed with the authorization flow! So make sure that somehow, you retrieve this code!

With this code, thanks to the createAsync() method, you're able to create your api object:

const api = await osu.API.createAsync("<client_id>", "<client_secret>", {code: "<code>", redirect_uri: "<application_callback_url>"})

Keep your api object somewhere safe

Congrats on making your api object! As you may have seen from glancing at the documentation, it is pretty important as it holds a lot of information, such as:

  • the access_token used for all requests and the refresh_token used to get a new access_token without asking the user for consent again
  • expires, which tells you when your access_token will expire
  • user, which is the id of the osu! user who has gone through that whole authorization thingie

It is important to note that it has features you can change or turn off, they are all listed in the documentation but those worth noting are:

Finally, do note that you can use the refresh_token yourself using refreshToken()!

Full example

Here's a full example where you will launch a server that will:

  • listen for an authorization code
  • create the authorization URL and open it in your browser
  • create the api object with the authorization code it has received
  • get your user id and username from that
// TypeScript
import * as osu from "osu-api-v2-js"
import * as http from "http"
import { exec } from "child_process"

// This should be from an application registered on https://osu.ppy.sh/home/account/edit#oauth
const id = 0 // replace with your client id
const secret = "<client_secret>"
const redirect_uri = "<application_callback_url>" // assuming localhost with any unused port for convenience (like http://localhost:7272/)

// Because we need to act as an authenticated user, we need to go through the authorization procedure
// This function largely takes care of it by itself
async function getCode(authorization_url: string): Promise<string> {
	// Open a temporary server to receive the code when the browser is sent to the redirect_uri after confirming authorization
	const httpserver = http.createServer()
	const host = redirect_uri.substring(redirect_uri.indexOf("/") + 2, redirect_uri.lastIndexOf(":"))
	const port = Number(redirect_uri.substring(redirect_uri.lastIndexOf(":") + 1).split("/")[0])
	httpserver.listen({host, port})

	// Open the browser to the page on osu!web where you click a button to say you authorize your application
	console.log("Waiting for code...")
	const command = (process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open")
	exec(`${command} "${authorization_url}"`)

	// Check the URL for a `code` GET parameter, get it if it's there
	const code: string = await new Promise((resolve) => {
		httpserver.on("request", (request, response) => {
			if (request.url) {
				console.log("Received code!")
				response.end("Worked! You may now close this tab.", "utf-8")
				httpserver.close() // Close the temporary server as it is no longer needed
				resolve(request.url.substring(request.url.indexOf("code=") + 5))
			}
		})
	})
	return code
}

async function getSelf() {
	// Get the code needed for the api object
	const url = osu.generateAuthorizationURL(id, redirect_uri, ["public", "identify"])
	const code = await getCode(url)
	const api = await osu.API.createAsync(id, secret, {code, redirect_uri}, {verbose: "all"})
	
	// Use the `me` endpoint, which gives information about the authorized user!
	const me = await api.getResourceOwner()
	console.log("My id is", me.id, "but I'm better known as", me.username)
	
	// If you're not gonna use the token anymore, might as well revoke it for the sake of security
	await api.revokeToken().then(() => console.log("Revoked the token, it can no longer be used!"))
}

getSelf()

If you're looking for an example that involves WebSockets, you might wanna take a look at lib/tests/websocket.ts in the package's repository!

Calling the functions, but literally

This package's functions can be accessed both through the api object and through namespaces! It essentially means that for convenience's sake, there are two ways to do anything:

// Obtaining a match, assuming an `api` object already exists and everything from the package is imported as `osu`
const match_1 = await api.getMatch(103845156) // through the api object
const match_2 = await osu.Match.getOne.call(api, 103845156) // through the namespaces
// `match_1` and `match_2` are the same, because they're essentially using the same function!

// The same, but for obtaining multiple lazer updates
const builds_1 = await api.getChangelogBuilds("lazer")
const builds_2 = await osu.Changelog.Build.getMultiple.call(api, "lazer")
// `build_1` and `build_2` are also the same!

As you may have noticed, when calling the functions through the namespaces, instead of doing something like getOne(), we instead do getOne.call() and use the call() method in order to provide a this value; the api object!

Of course, using the apply() method would also work, so just do things the way you prefer or the way that is more intuitive to you!

List of implemented endpoints

In the same order as on the API's official documentation:

Beatmap Packs

Beatmaps

Beatmapset Discussions

Beatmapsets

Changelog

Chat

Comments

  • GET /comments -> getComments()
  • GET /comments/{comment} -> getComment()
  • While other relevant endpoints exist, they are only officially supported through the osu! client (lazer)

Events

Forum

Home

Matches

Multiplayer

  • GET /rooms/{room}/playlist/{playlist}/scores -> getPlaylistItemScores()
  • GET /rooms -> getRooms()
  • While other relevant endpoints exist, they are only officially supported through the osu! client (lazer)

News

Ranking

Scores

Under "Undocumented" or missing from docs

Users

Wiki

About

Package to easily access the new api of osu!

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •