From 365b6bd38156be1ae45ce66c34601d4cc76d2aea Mon Sep 17 00:00:00 2001 From: DaInfLoop Date: Mon, 17 Feb 2025 21:20:02 +0000 Subject: [PATCH] AAAAAAAAAAAAAAAAAAAAAAAAAAAA REPLAYS --- .gitignore | 2 + bun.lockb | Bin 100630 -> 105667 bytes index.ts | 294 ++++++++++++++++++++++++++++++++++++++++++++++----- package.json | 5 +- 4 files changed, 273 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 9b1ee42..168cbee 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,5 @@ dist # Finder (MacOS) folder config .DS_Store + +replay-*.osr \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 0b0866a4b08201dc26c41bf32eb82627cd3563f7..3565769780558b1cb98d78429f8ced7097a8ee16 100755 GIT binary patch delta 22476 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 diff --git a/index.ts b/index.ts index bf691f3..d4b95d8 100644 --- a/index.ts +++ b/index.ts @@ -8,6 +8,13 @@ import type { StaticSelectAction } from "@slack/bolt"; import { inspect } from "node:util"; import { scheduleJob } from "node-schedule"; +// @ts-expect-error No typings :))))))))))) +import osr from "node-osr"; +import { Client, Events } from "ordr.js"; + +import io from "socket.io-client"; +import fs from "fs"; + const sql = postgres({ host: '/var/run/postgresql', database: 'haroon_osu', @@ -25,6 +32,8 @@ const app = new App({ } }); +const ordr = new Client(process.env.ORDR_TOKEN!); + const states = new Map(); app.command("/osu-link", async (ctx) => { @@ -168,7 +177,6 @@ async function getAccessToken(slack_id: string): Promise { if (!user.length) return null - try { const data = await fetch("https://osu.ppy.sh/oauth/token", { method: "POST", @@ -178,8 +186,6 @@ async function getAccessToken(slack_id: string): Promise { 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()); - console.log(data) - await sql`UPDATE links SET refresh_token = ${data.refresh_token} WHERE slack_id = ${slack_id}`; return data.access_token; @@ -211,20 +217,26 @@ function splitArray(arr: T[], maxElements: number): T[][] { } /// GENERATED /// -const cache: { - username: string, - id: number, - slackId: string, +type CacheUser = { + username: string; + id: number; + slackId: string; score: { - osu: number, - taiko: number - fruits: number, - mania: number - } -}[] = [] + osu: number; + taiko: number; + fruits: number; + mania: number; + }; +} + +const cache: CacheUser[] = [] const multiplayerRoundCache: any[] = []; +const sentWarningDM = { + ref: false +} + async function cacheStuff(): Promise { const token = await getTemporaryToken(); @@ -273,7 +285,49 @@ async function cacheStuff(): Promise { const tohken = await getAccessToken("U06TBP41C3E") as string; - if (!tohken) return; + if (!tohken) { + const verifCode = `OSULEADERBOARD-U06TBP41C3E-${Date.now()}`; + + states.set('U06TBP41C3E', verifCode); + + const encodedCode = await bcrypt.hash(verifCode, 10); + + await app.client.chat.postMessage({ + channel: "U06TBP41C3E", + text: "uh oh, your token seems to have expired!! multiplayer round fetching + daily challenges are disabled.", + blocks: [ + { + type: 'section', + text: { + type: "mrkdwn", + text: `uh oh, your token seems to have expired!! multiplayer round fetching + daily challenges are disabled.` + } + }, + { + type: 'section', + text: { + type: "mrkdwn", + text: `Please re-authenticate to generate it by clicking the Reauthenticate button.` + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Reauthenticate", + "emoji": true + }, + "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("U06TBP41C3E:" + encodedCode)}&scope=public`, + "action_id": "link" + } + }, + ] + }) + + sentWarningDM.ref = true; + + return + }; const rooms = await fetch(`https://osu.ppy.sh/api/v2/rooms?category=realtime`, { headers: { @@ -489,16 +543,17 @@ app.command('/osu-profile', async (ctx) => { app.command('/osu-leaderboard', async (ctx) => { await ctx.ack(); - const cached = splitArray(cache.sort((a, b) => { + const cached = splitArray(cache.sort((a, b) => { return b.score.osu - a.score.osu }), 10); const users = []; - for (let i in cached) { + for (let i in cached[0]) { try { - const cachedU = cached[i]; - const slackProfile = (await ctx.client.users.info({ user: cachedU.slackId })).user!; + const cachedU = cached[0][i]; + const slackInfo = await ctx.client.users.info({ user: cachedU.slackId }) + const slackProfile = slackInfo.user!; users.push(`${users.length + 1}. / - ${cachedU.score.osu.toLocaleString()}`) } catch (e) { @@ -602,7 +657,7 @@ app.action(/change-leaderboard\|.+/, async (ctx) => { return ctx.respond({ replace_original: false, response_type: "ephemeral", text: `This leaderboard was initialised by <@${userId}>. Only they can manage it.` }) } - const selected = action.selected_option.value; + const selected = action.selected_option.value as "osu" | "taiko" | "fruits" | "mania"; const cached = splitArray(cache.sort((a, b) => { return b.score[selected] - a.score[selected] @@ -878,6 +933,41 @@ app.command('/osu-search', async (ctx) => { }); } + const accessToken = await getAccessToken(ctx.context.userId!); + + if (!accessToken) { + const verifCode = `OSULEADERBOARD-${ctx.context.userId}-${Date.now()}`; + + states.set(ctx.context.userId, verifCode); + + const encodedCode = await bcrypt.hash(verifCode, 10); + + return ctx.respond({ + response_type: 'ephemeral', + text: `Hey <@${ctx.context.userId}>, your token has expired. Please re-authenticate to generate it by clicking the Reauthenticate button.`, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `Hey <@${ctx.context.userId}>, your token has expired. Please re-authenticate to generate it by clicking the \`Reauthenticate\` button.` + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Reauthenticate", + "emoji": true + }, + "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)}&scope=public`, + "action_id": "link" + } + }, + ] + }); + } + ctx.client.views.open({ trigger_id: ctx.payload.trigger_id, view: { @@ -1076,7 +1166,7 @@ app.view("search", async (ctx) => { const set = data.beatmapsets[0]; - return ctx.client.chat.postMessage({ + ctx.client.chat.postMessage({ channel: ctx.view.private_metadata, "blocks": [ { @@ -1194,14 +1284,164 @@ async function debugDailyChallenge() { }) } -; (async () => { - await app.start(41691); +type QueueJob = { + md5: string, + playerName: string, + ts: string, + userId: string +} - console.log('⚡️ Bolt app is running!'); +const queue: QueueJob[] = [] +// key is renderID +const waiting = new Map() - cacheStuff(); +const processQueue = async () => { + if (queue.length > 0) { + const job = queue.shift()!; - setInterval(cacheStuff, 60 * 1000) // Cache every minute. Ratelimit is 1200 req/m anyways. + setTimeout(processQueue, 5000) - scheduleJob('30 5 0 * * *', debugDailyChallenge) -})(); + app.client.reactions.add({ + channel: "C165V7XT9", + name: "thinkspin", + timestamp: job.ts + }) + + const render = await ordr.sendRender({ + replay: `replay-${job.md5}.osr`, + skin: 'default', + username: job.playerName, + showDanserLogo: false, + resolution: '1280x720' + }) + + console.log(render) + + // @ts-ignore Error code 0 DOES exist: https://ordr.issou.best/docs/#section/Error-codes + if (render.errorCode !== 0) { + app.client.chat.postEphemeral({ + channel: "C165V7XT9", + user: job.userId, + text: `Hey: it looks like you posted a replay! Unfortunately, I couldn't generate a video of it: "${render.message}"` + }) + return + } + + waiting.set(render.renderID!, job); + } +}; + +const socket = io('https://apis.issou.best', { + path: '/ordr/ws', + autoConnect: false +}) + +socket.on('connect', () => { + console.log('Connected to ordr websocket!') +}) + +socket.on('disconnect', reason => { + if (reason == "io server disconnect") { + socket.connect() + } +}) + +socket.on('render_done_json', async (render) => { + const job = waiting.get(render.renderID!); + + if (!job) return; + + app.client.chat.postMessage({ + channel: 'C165V7XT9', + thread_ts: job.ts, + reply_broadcast: true, + text: `<${render.videoUrl}|replay-${job.md5}.mp4>`, + unfurl_media: true + }) + + app.client.reactions.remove({ + channel: "C165V7XT9", + name: "thinkspin", + timestamp: job.ts + }) + + waiting.delete(render.renderID!); +}) + +const addToQueue = (job: QueueJob) => { + queue.push(job); + if (queue.length === 1) { + processQueue(); + } +}; + +app.event("message", async (ctx) => { + if (ctx.event.channel != "C165V7XT9") return; + if (ctx.event.subtype != "file_share") return; + const ts = ctx.event.ts; + + const history = await ctx.client.conversations.history({ + channel: "C165V7XT9", + latest: ts, + limit: 1, + inclusive: true + }) + + if (!(history.messages && history.messages.length > 0)) { + return; + } + + const message = history.messages[0]; + + if (!message.files) return; + if (message.files.length === 0) return; + + const replay = message.files.find(file => file.name?.endsWith(".osr")); + + if (!replay) return; + + const replayData = await fetch(replay.url_private_download!, { + headers: { + 'Authorization': `Bearer ${process.env.SLACK_BOT_TOKEN}` + } + }).then(res => res.arrayBuffer()); + + const replayBuffer = Buffer.from(replayData); + + const _replay = await osr.read(replayBuffer); + + if (_replay.gameMode !== 0) { + ctx.client.chat.postEphemeral({ + channel: "C165V7XT9", + user: ctx.context.userId!, + text: "Hey: it looks like you posted a replay! Unfortunately, it's not an :osu-standard: osu!standard replay, and so I can't generate a video of it. Sorry!" + }) + return; + } + + const replayFile = fs.createWriteStream(`replay-${_replay.replayMD5}.osr`); + + replayFile.write(replayBuffer); + replayFile.end(); + + addToQueue({ + md5: _replay.replayMD5, + playerName: _replay.playerName, + ts: ts, + userId: ctx.context.userId! + }) +}) + + ; (async () => { + await app.start(41691); + + console.log('⚡️ Bolt app is running!'); + + socket.connect(); + + cacheStuff(); + + setInterval(cacheStuff, 60 * 1000) // Cache every minute. Ratelimit is 1200 req/m anyways. + + scheduleJob('30 5 0 * * *', debugDailyChallenge) + })(); diff --git a/package.json b/package.json index ceec3e2..908df1e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "@types/node-schedule": "^2.1.7", "bcrypt": "^5.1.1", "dotenv": "^16.4.5", + "node-osr": "^1.2.1", "node-schedule": "^2.1.1", - "postgres": "^3.4.4" + "ordr.js": "^4.0.0", + "postgres": "^3.4.4", + "socket.io-client": "^4.8.1" } } \ No newline at end of file