Two teams try and fill in any horizontal, vertical, or diagonal line on a bingo board by playing maps on osu!
osu.bingo
osu
1import { logger } from '$lib/logger';
2import type { Osu } from '../osu';
3
4const BASE_URL = 'https://osu.ppy.sh/api/v2';
5const AUTH_URL = 'https://osu.ppy.sh/oauth/authorize';
6const TOKEN_URL = 'https://osu.ppy.sh/oauth/token';
7
8export const createAuthUrl = (
9 client_id: string,
10 redirect_uri: string,
11 scopes: Osu.AuthScope[],
12 state?: string
13) => {
14 const params = new URLSearchParams();
15 params.set('client_id', client_id);
16 params.set('scope', scopes.join(' '));
17 params.set('response_type', 'code');
18 params.set('redirect_uri', redirect_uri);
19 if (state) {
20 params.set('state', state);
21 }
22
23 return `${AUTH_URL}?${params.toString()}`;
24};
25
26export const exchangeAuthCode = async (
27 code: string,
28 client_id: string,
29 client_secret: string,
30 redirect_uri: string
31): Promise<Osu.AuthorizationCodeTokenResponse> => {
32 const body = {
33 client_id,
34 client_secret,
35 code,
36 grant_type: 'authorization_code',
37 redirect_uri
38 };
39
40 const tokenReqUrl = `${TOKEN_URL}`;
41 const tokenReq = await fetch(tokenReqUrl, {
42 headers: {
43 Accept: 'application/json',
44 'Content-Type': 'application/json'
45 },
46 method: 'POST',
47 body: JSON.stringify(body)
48 });
49 return tokenReq.json();
50};
51
52export const getMe = async (access_token: string): Promise<Osu.User> => {
53 const userReq = await fetch(`${BASE_URL}/me`, {
54 headers: {
55 'Content-Type': 'application/json',
56 Accept: 'application/json',
57 Authorization: `Bearer ${access_token}`
58 }
59 });
60 return userReq.json();
61};
62
63// This is a very basic beatmap fetcher
64// Something else should be used in the future to increase
65// The quality of the maps.
66export const getBeatmaps = async (min_sr: number, max_sr: number, access_token: string) => {
67 const params = new URLSearchParams();
68 params.set('q', `stars>${min_sr} stars<${max_sr}`);
69
70 const beatmapSets = await fetch(`${BASE_URL}/beatmapsets/search?${params.toString()}`, {
71 headers: {
72 'Content-Type': 'application/json',
73 Accept: 'application/json',
74 Authorization: `Bearer ${access_token}`
75 }
76 });
77 const response = await beatmapSets.json();
78 const beatmapsets: Osu.Beatmapset[] = response.beatmapsets;
79
80 return beatmapsets;
81};
82
83export const refreshOAuthToken = async (
84 token: Bingo.OauthToken,
85 client_id: string,
86 client_secret: string
87): Promise<Osu.AuthorizationCodeTokenResponse | null> => {
88 logger.info(`Updating osu oauth token of ${token.user_id}`, { type: 'token_update' });
89 const refreshBody = {
90 client_id,
91 client_secret,
92 grant_type: 'refresh_token',
93 refresh_token: token.refresh_token
94 };
95
96 const refreshReq = await fetch(TOKEN_URL, {
97 method: 'POST',
98 body: JSON.stringify(refreshBody),
99 headers: {
100 'Content-Type': 'application/json',
101 Accept: 'application/json'
102 }
103 });
104
105 const updatedToken = await refreshReq.json();
106
107 // Successfully updated
108 if (refreshReq.ok && updatedToken.access_token) {
109 return updatedToken;
110 }
111 return null;
112};
113
114export const getRecentScores = async (user_id: number, access_token: string) => {
115 const params = new URLSearchParams();
116 params.set('include_fails', '1');
117
118 const scores = await fetch(`${BASE_URL}/users/${user_id}/scores/recent?${params.toString()}`, {
119 headers: {
120 'Content-Type': 'application/json',
121 Accept: 'application/json',
122 Authorization: `Bearer ${access_token}`,
123 'x-api-version': '20220705' // Use lazer api
124 }
125 });
126 const response: Osu.LazerScore[] = await scores.json();
127 if (scores.ok) {
128 logger.debug(`[${user_id}]: Successfully fetched scores`, {
129 type: 'fetch_user_scores',
130 user_id
131 });
132 return response;
133 }
134 return null;
135};