mirror of
https://git.haroon.hackclub.app/haroon/osu-leaderboard.git
synced 2024-11-23 21:23:40 +00:00
make a base for multiplayer joining
This commit is contained in:
parent
9297fdb776
commit
e6aacd6897
1 changed files with 230 additions and 43 deletions
273
index.ts
273
index.ts
|
@ -23,6 +23,122 @@ const app = new App({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// GENERATED ///
|
||||||
|
type Room = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
type: string;
|
||||||
|
user_id: number;
|
||||||
|
starts_at: string;
|
||||||
|
ends_at: string | null;
|
||||||
|
max_attempts: number | null;
|
||||||
|
participant_count: number;
|
||||||
|
channel_id: number;
|
||||||
|
active: boolean;
|
||||||
|
has_password: boolean;
|
||||||
|
queue_mode: string;
|
||||||
|
auto_skip: boolean;
|
||||||
|
current_playlist_item: {
|
||||||
|
id: number;
|
||||||
|
room_id: number;
|
||||||
|
beatmap_id: number;
|
||||||
|
ruleset_id: number;
|
||||||
|
allowed_mods: any[];
|
||||||
|
required_mods: {
|
||||||
|
acronym: string;
|
||||||
|
settings: Record<string, unknown>;
|
||||||
|
}[];
|
||||||
|
expired: boolean;
|
||||||
|
owner_id: number;
|
||||||
|
playlist_order: number | null;
|
||||||
|
played_at: string | null;
|
||||||
|
beatmap: {
|
||||||
|
beatmapset_id: number;
|
||||||
|
difficulty_rating: number;
|
||||||
|
id: number;
|
||||||
|
mode: string;
|
||||||
|
status: string;
|
||||||
|
total_length: number;
|
||||||
|
user_id: number;
|
||||||
|
version: string;
|
||||||
|
beatmapset: {
|
||||||
|
artist: string;
|
||||||
|
artist_unicode: string;
|
||||||
|
covers: {
|
||||||
|
cover: string;
|
||||||
|
cover2x: string;
|
||||||
|
card: string;
|
||||||
|
card2x: string;
|
||||||
|
list: string;
|
||||||
|
list2x: string;
|
||||||
|
slimcover: string;
|
||||||
|
slimcover2x: string;
|
||||||
|
};
|
||||||
|
creator: string;
|
||||||
|
favourite_count: number;
|
||||||
|
hype: number | null;
|
||||||
|
id: number;
|
||||||
|
nsfw: boolean;
|
||||||
|
offset: number;
|
||||||
|
play_count: number;
|
||||||
|
preview_url: string;
|
||||||
|
source: string;
|
||||||
|
spotlight: boolean;
|
||||||
|
status: string;
|
||||||
|
title: string;
|
||||||
|
title_unicode: string;
|
||||||
|
track_id: number | null;
|
||||||
|
user_id: number;
|
||||||
|
video: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
difficulty_range: {
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
};
|
||||||
|
host: {
|
||||||
|
avatar_url: string;
|
||||||
|
country_code: string;
|
||||||
|
default_group: string;
|
||||||
|
id: number;
|
||||||
|
is_active: boolean;
|
||||||
|
is_bot: boolean;
|
||||||
|
is_deleted: boolean;
|
||||||
|
is_online: boolean;
|
||||||
|
is_supporter: boolean;
|
||||||
|
last_visit: string;
|
||||||
|
pm_friends_only: boolean;
|
||||||
|
profile_colour: string | null;
|
||||||
|
username: string;
|
||||||
|
country: {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
playlist_item_stats: {
|
||||||
|
count_active: number;
|
||||||
|
count_total: number;
|
||||||
|
ruleset_ids: number[];
|
||||||
|
};
|
||||||
|
recent_participants: {
|
||||||
|
avatar_url: string;
|
||||||
|
country_code: string;
|
||||||
|
default_group: string;
|
||||||
|
id: number;
|
||||||
|
is_active: boolean;
|
||||||
|
is_bot: boolean;
|
||||||
|
is_deleted: boolean;
|
||||||
|
is_online: boolean;
|
||||||
|
is_supporter: boolean;
|
||||||
|
last_visit: string;
|
||||||
|
pm_friends_only: boolean;
|
||||||
|
profile_colour: string | null;
|
||||||
|
username: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
/// GENERATED ///
|
||||||
|
|
||||||
const states = new Map();
|
const states = new Map();
|
||||||
|
|
||||||
|
@ -74,7 +190,7 @@ app.command("/osu-link", async (ctx) => {
|
||||||
"emoji": true
|
"emoji": true
|
||||||
},
|
},
|
||||||
"value": "link",
|
"value": "link",
|
||||||
"url": `https://osu.ppy.sh/oauth/authorize?client_id=33126&redirect_uri=https://osu.haroon.hackclub.app/osu/callback&response_type=code&state=${encodeURIComponent(ctx.context.userId + ":" + encodedCode)}`,
|
"url": `https://osu.ppy.sh/oauth/authorize?client_id=33126&redirect_uri=https://osu.haroon.hackclub.app/osu/callback&response_type=code&state=${encodeURIComponent(ctx.context.userId + ":" + encodedCode)}&scope=public`,
|
||||||
"action_id": "link"
|
"action_id": "link"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +232,7 @@ receiver.router.get("/osu/callback", async (req, res) => {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
},
|
},
|
||||||
body: `client_id=33126&client_secret=${encodeURIComponent(process.env.CLIENT_SECRET!)}&code=${code}&grant_type=authorization_code&redirect_uri=${encodeURIComponent("https://osu.haroon.hackclub.app/osu/callback")}`
|
body: `client_id=33126&client_secret=${encodeURIComponent(process.env.CLIENT_SECRET!)}&code=${code}&grant_type=authorization_code&scope=public&redirect_uri=${encodeURIComponent("https://osu.haroon.hackclub.app/osu/callback")}`
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
@ -132,9 +248,9 @@ receiver.router.get("/osu/callback", async (req, res) => {
|
||||||
// {user.id} - osu! user ID
|
// {user.id} - osu! user ID
|
||||||
// userId - slack user ID
|
// userId - slack user ID
|
||||||
|
|
||||||
await sql`INSERT INTO links VALUES (${user.id}, ${_userId})`
|
await sql`INSERT INTO links VALUES (${user.id}, ${_userId}, ${data.refresh_token})`
|
||||||
|
|
||||||
getLeaderboard();
|
cacheStuff();
|
||||||
|
|
||||||
return res.send(`Your osu! account (${user.id}) has been successfully linked to your Slack account (${_userId})!`)
|
return res.send(`Your osu! account (${user.id}) has been successfully linked to your Slack account (${_userId})!`)
|
||||||
}
|
}
|
||||||
|
@ -162,6 +278,34 @@ async function getTemporaryToken(): Promise<string> {
|
||||||
return data.access_token;
|
return data.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAccessToken(slack_id: string): Promise<string> {
|
||||||
|
const user = await sql`SELECT * FROM links WHERE slack_id = ${slack_id}`;
|
||||||
|
|
||||||
|
const data = await fetch("https://osu.ppy.sh/oauth/token", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: `client_id=33126&client_secret=${encodeURIComponent(process.env.CLIENT_SECRET!)}&grant_type=refresh_token&refresh_token=${user[0].refresh_token}&scope=public`
|
||||||
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
sql`UPDATE links SET refresh_token = ${data.refresh_token} WHERE slack_id = ${slack_id}`;
|
||||||
|
|
||||||
|
return data.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendGET<T>(path: string, token?: string): Promise<T> {
|
||||||
|
const _token = token || await getTemporaryToken();
|
||||||
|
|
||||||
|
const data = await fetch(`https://osu.ppy.sh/api/v2/${path}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${_token}`
|
||||||
|
}
|
||||||
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
return data as T
|
||||||
|
}
|
||||||
|
|
||||||
/// GENERATED ///
|
/// GENERATED ///
|
||||||
function splitArray<T>(arr: T[], maxElements: number): T[][] {
|
function splitArray<T>(arr: T[], maxElements: number): T[][] {
|
||||||
const result: T[][] = [];
|
const result: T[][] = [];
|
||||||
|
@ -184,29 +328,9 @@ const cache: {
|
||||||
}
|
}
|
||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
async function getLeaderboard(): Promise<{
|
const multiplayerRoundCache: Room[] = [];
|
||||||
username: string,
|
|
||||||
id: number,
|
async function cacheStuff(): Promise<void> {
|
||||||
slackId: string,
|
|
||||||
score: {
|
|
||||||
osu: number,
|
|
||||||
taiko: number
|
|
||||||
fruits: number,
|
|
||||||
mania: number
|
|
||||||
}
|
|
||||||
}[]>
|
|
||||||
async function getLeaderboard(sortBy: "osu" | "taiko" | "fruits" | "mania", asc?: boolean): Promise<{
|
|
||||||
username: string,
|
|
||||||
id: number,
|
|
||||||
slackId: string,
|
|
||||||
score: {
|
|
||||||
osu: number,
|
|
||||||
taiko: number
|
|
||||||
fruits: number,
|
|
||||||
mania: number
|
|
||||||
}
|
|
||||||
}[]>
|
|
||||||
async function getLeaderboard(sortBy?: "osu" | "taiko" | "fruits" | "mania", asc: boolean = true) {
|
|
||||||
const token = await getTemporaryToken();
|
const token = await getTemporaryToken();
|
||||||
|
|
||||||
const users = await sql`SELECT * FROM links`;
|
const users = await sql`SELECT * FROM links`;
|
||||||
|
@ -228,11 +352,7 @@ async function getLeaderboard(sortBy?: "osu" | "taiko" | "fruits" | "mania", asc
|
||||||
for (let list of splitArray<string[]>(osuUsers, 50)) {
|
for (let list of splitArray<string[]>(osuUsers, 50)) {
|
||||||
const query = list.map((user) => `ids[]=${user[0]}`).join("&");
|
const query = list.map((user) => `ids[]=${user[0]}`).join("&");
|
||||||
|
|
||||||
const data = await fetch(`https://osu.ppy.sh/api/v2/users?${query}`, {
|
const data = await sendGET(`users?${query}`)
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
}
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
// @ts-ignore i can't be bothered to type this rn
|
// @ts-ignore i can't be bothered to type this rn
|
||||||
lb.push(...data.users.map(user => ({
|
lb.push(...data.users.map(user => ({
|
||||||
|
@ -252,14 +372,17 @@ async function getLeaderboard(sortBy?: "osu" | "taiko" | "fruits" | "mania", asc
|
||||||
|
|
||||||
cache.push(...lb);
|
cache.push(...lb);
|
||||||
|
|
||||||
if (sortBy) {
|
// Multiplayer games
|
||||||
lb = lb.sort((a, b) => {
|
|
||||||
if (asc) return b.score[sortBy] - a.score[sortBy]
|
|
||||||
else return a.score[sortBy] - b.score[sortBy]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return lb
|
multiplayerRoundCache.length = 0;
|
||||||
|
|
||||||
|
const rooms = await fetch(`https://osu.ppy.sh/api/v2/rooms?category=realtime`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${await getAccessToken("U06TBP41C3E")}`
|
||||||
|
}
|
||||||
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
multiplayerRoundCache.push(...rooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateProfile(slackProfile: User) {
|
async function generateProfile(slackProfile: User) {
|
||||||
|
@ -416,7 +539,32 @@ app.command('/osu-profile', async (ctx) => {
|
||||||
|
|
||||||
if (!cache.find(u => u.slackId == slackProfile.id)) {
|
if (!cache.find(u => u.slackId == slackProfile.id)) {
|
||||||
return ctx.respond({
|
return ctx.respond({
|
||||||
text: `You don't seem to have an osu! account linked. You might have to wait a bit for my cache to reload though.`
|
response_type: 'in_channel',
|
||||||
|
text: `${userProfile.display_name_normalized} ran /osu-profile`,
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
"type": "context",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": `<@${ctx.context.userId}> ran \`/osu-profile\` | Matched by no input`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"text": {
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": `*Slack Username*: <https://hackclub.slack.com/team/${slackProfile.id}|${slackProfile.profile!.display_name_normalized}>\n*osu! username:* Not linked`
|
||||||
|
},
|
||||||
|
"accessory": {
|
||||||
|
"type": "image",
|
||||||
|
"image_url": 'https://osu.ppy.sh/images/layout/avatar-guest@2x.png',
|
||||||
|
"alt_text": `default osu profile picture`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,6 +795,45 @@ app.action(/change-leaderboard\|.+/, async (ctx) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.command("/osu-multiplayer-invite", async (ctx) => {
|
||||||
|
await ctx.ack();
|
||||||
|
const me = cache.find(user => user.slackId == ctx.context.userId);
|
||||||
|
|
||||||
|
if (!me) {
|
||||||
|
return ctx.respond({
|
||||||
|
response_type: 'ephemeral',
|
||||||
|
text: `Hey <@${ctx.context.userId}>, you haven't linked your osu! account to your Slack account. Run /osu-link and then run this command.`,
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
text: {
|
||||||
|
type: 'mrkdwn',
|
||||||
|
text: `Hey <@${ctx.context.userId}>, you haven't linked your osu! account to your Slack account. Run \`/osu-link\` and then run this command.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownedRoom = multiplayerRoundCache.find(room => room.host.id == me.id);
|
||||||
|
|
||||||
|
if (!me) {
|
||||||
|
return ctx.respond({
|
||||||
|
response_type: 'ephemeral',
|
||||||
|
text: `Hey <@${ctx.context.userId}>, you aren't in a multiplayer room. If you are, make sure you're the host of the room, and you're on osu!lazer.`,
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
text: {
|
||||||
|
type: 'mrkdwn',
|
||||||
|
text: `Hey <@${ctx.context.userId}>, you aren't in a multiplayer room. If you are, make sure you're the host of the room, and you're on osu!lazer.`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
receiver.router.get('/osu/news.rss', async (req, res) => {
|
receiver.router.get('/osu/news.rss', async (req, res) => {
|
||||||
const news = await fetch('https://osu.ppy.sh/api/v2/news').then(res => res.json());
|
const news = await fetch('https://osu.ppy.sh/api/v2/news').then(res => res.json());
|
||||||
|
|
||||||
|
@ -674,7 +861,7 @@ receiver.router.get('/osu/news.rss', async (req, res) => {
|
||||||
<guid isPermaLink="false">${post.id}</guid>
|
<guid isPermaLink="false">${post.id}</guid>
|
||||||
<pubDate>${new Date(post.published_at).toLocaleString('en-GB', {timeZone: 'UTC',hour12: false,weekday: 'short',year: 'numeric',month: 'short',day: '2-digit',hour: '2-digit',minute: '2-digit',second: '2-digit',}).replace(/(?:(\d),)/, '$1') + ' GMT'}</pubDate>
|
<pubDate>${new Date(post.published_at).toLocaleString('en-GB', {timeZone: 'UTC',hour12: false,weekday: 'short',year: 'numeric',month: 'short',day: '2-digit',hour: '2-digit',minute: '2-digit',second: '2-digit',}).replace(/(?:(\d),)/, '$1') + ' GMT'}</pubDate>
|
||||||
<description>${post.preview}</description>
|
<description>${post.preview}</description>
|
||||||
<enclosure url="${post.first_image}" length="0" type="image/jpg"/>
|
<enclosure url="${post.first_image}" type="image/jpg"/>
|
||||||
</item>`
|
</item>`
|
||||||
).join('\n ')}
|
).join('\n ')}
|
||||||
</channel>
|
</channel>
|
||||||
|
@ -693,7 +880,7 @@ receiver.router.get('*', (req, res) => {
|
||||||
|
|
||||||
console.log('⚡️ Bolt app is running!');
|
console.log('⚡️ Bolt app is running!');
|
||||||
|
|
||||||
getLeaderboard();
|
cacheStuff();
|
||||||
|
|
||||||
setTimeout(getLeaderboard, 5 * 60 * 1000)
|
setTimeout(cacheStuff, 60 * 1000) // Cache every minute. Ratelimit is 1200 req/m anyways.
|
||||||
})();
|
})();
|
Loading…
Reference in a new issue