mirror of
https://github.com/ershisan99/www.git
synced 2025-12-18 05:19:23 +00:00
175 lines
4.5 KiB
TypeScript
175 lines
4.5 KiB
TypeScript
import { createTRPCRouter, publicProcedure } from '@/server/api/trpc'
|
|
import { db } from '@/server/db'
|
|
import { metadata, player_games, raw_history } from '@/server/db/schema'
|
|
import { desc, eq } from 'drizzle-orm'
|
|
import ky from 'ky'
|
|
import { chunk } from 'remeda'
|
|
import { z } from 'zod'
|
|
|
|
export const history_router = createTRPCRouter({
|
|
user_games: publicProcedure
|
|
.input(
|
|
z.object({
|
|
user_id: z.string(),
|
|
})
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db
|
|
.select()
|
|
.from(player_games)
|
|
.where(eq(player_games.playerId, input.user_id))
|
|
.orderBy(desc(player_games.gameNum))
|
|
}),
|
|
sync: publicProcedure.mutation(async () => {
|
|
return syncHistory()
|
|
}),
|
|
})
|
|
|
|
export async function syncHistory() {
|
|
const cursor = await db
|
|
.select()
|
|
.from(metadata)
|
|
.where(eq(metadata.key, 'history_cursor'))
|
|
.limit(1)
|
|
.then((res) => res[0])
|
|
const data = await ky
|
|
.get('https://api.neatqueue.com/api/history/1226193436521267223', {
|
|
searchParams: {
|
|
start_game_number: cursor?.value ?? 1,
|
|
},
|
|
timeout: 60000,
|
|
})
|
|
.json<any>()
|
|
const matches = await fetch(
|
|
'https://api.neatqueue.com/api/matches/1226193436521267223'
|
|
).then((res) => res.json())
|
|
const firstGame = Object.keys(matches).sort(
|
|
(a, b) => Number.parseInt(a) - Number.parseInt(b)
|
|
)[0]
|
|
if (!firstGame) {
|
|
throw new Error('No first game found')
|
|
}
|
|
|
|
await db
|
|
.insert(metadata)
|
|
.values({
|
|
key: 'history_cursor',
|
|
value: firstGame,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: metadata.key,
|
|
set: {
|
|
key: 'history_cursor',
|
|
value: firstGame,
|
|
},
|
|
})
|
|
|
|
const chunkedData = chunk(data.data, 100)
|
|
for (const chunk of chunkedData) {
|
|
await insertGameHistory(chunk).catch((e) => {
|
|
console.error(e)
|
|
})
|
|
}
|
|
return data
|
|
}
|
|
|
|
function processGameEntry(gameId: number, game_num: number, entry: any) {
|
|
const parsedEntry = typeof entry === 'string' ? JSON.parse(entry) : entry
|
|
if (parsedEntry.game === '1v1-attrition') {
|
|
return []
|
|
}
|
|
if (!parsedEntry.teams?.[0]?.[0] || !parsedEntry.teams?.[1]?.[0]) {
|
|
console.log('skipping game', parsedEntry)
|
|
return []
|
|
}
|
|
|
|
if (parsedEntry.winner === -2) {
|
|
console.log('skipping ongoing game', parsedEntry)
|
|
return []
|
|
}
|
|
const player0 = parsedEntry.teams[0][0]
|
|
const player1 = parsedEntry.teams[1][0]
|
|
let p0result = null
|
|
let p1result = null
|
|
|
|
if (parsedEntry.winner === 2) {
|
|
p0result = 'tie'
|
|
p1result = 'tie'
|
|
} else if (parsedEntry.winner === 0) {
|
|
p0result = 'win'
|
|
p1result = 'loss'
|
|
} else if (parsedEntry.winner === 1) {
|
|
p0result = 'loss'
|
|
p1result = 'win'
|
|
} else {
|
|
p0result = 'unknown'
|
|
p1result = 'unknown'
|
|
}
|
|
return [
|
|
{
|
|
gameId,
|
|
gameNum: game_num,
|
|
gameTime: new Date(parsedEntry.time),
|
|
gameType: parsedEntry.game,
|
|
mmrChange: Number.parseFloat(player0.mmr_change),
|
|
opponentId: player1.id,
|
|
opponentMmr: Number.parseFloat(player1.mmr),
|
|
opponentName: player1.name,
|
|
playerId: player0.id,
|
|
playerMmr: Number.parseFloat(player0.mmr),
|
|
playerName: player0.name,
|
|
result: p0result,
|
|
won: parsedEntry.winner === 0,
|
|
},
|
|
{
|
|
gameId,
|
|
gameNum: game_num,
|
|
gameTime: new Date(parsedEntry.time),
|
|
gameType: parsedEntry.game,
|
|
mmrChange: Number.parseFloat(player1.mmr_change),
|
|
opponentId: player0.id,
|
|
opponentMmr: Number.parseFloat(player0.mmr),
|
|
opponentName: player0.name,
|
|
playerId: player1.id,
|
|
playerMmr: Number.parseFloat(player1.mmr),
|
|
playerName: player1.name,
|
|
result: p1result,
|
|
won: parsedEntry.winner === 1,
|
|
},
|
|
]
|
|
}
|
|
export async function insertGameHistory(entries: any[]) {
|
|
const rawResults = await Promise.all(
|
|
entries.map(async (entry) => {
|
|
return db
|
|
.insert(raw_history)
|
|
.values({ entry, game_num: entry.game_num })
|
|
.returning()
|
|
.onConflictDoUpdate({
|
|
target: raw_history.game_num,
|
|
set: {
|
|
entry,
|
|
},
|
|
})
|
|
.then((res) => res[0])
|
|
})
|
|
).then((res) => res.filter(Boolean))
|
|
|
|
const playerGameRows = rawResults.flatMap(({ entry, id, game_num }: any) => {
|
|
return processGameEntry(id, game_num, entry)
|
|
})
|
|
|
|
await Promise.all(
|
|
playerGameRows.map(async (row) => {
|
|
return db
|
|
.insert(player_games)
|
|
.values(row)
|
|
.onConflictDoUpdate({
|
|
target: [player_games.playerId, player_games.gameNum],
|
|
set: row,
|
|
})
|
|
.then((res) => res[0])
|
|
})
|
|
)
|
|
}
|