refactor: remove patch_detector and use gameVersion field in match_collector
This commit is contained in:
@@ -6,10 +6,21 @@ type Match = {
|
||||
}
|
||||
info: {
|
||||
endOfGameResult: string
|
||||
frameInterval: number
|
||||
gameCreation: number
|
||||
gameDuration: number
|
||||
gameEndTimestamp: number
|
||||
gameId: number
|
||||
gameMode: string
|
||||
gameName: string
|
||||
gameStartTimestamp: number
|
||||
gameType: string
|
||||
gameVersion: string
|
||||
mapId: number
|
||||
participants: Participant[]
|
||||
platformId: string
|
||||
queueId: number
|
||||
teams: Team[]
|
||||
tournamentCode: string
|
||||
}
|
||||
timeline: Timeline
|
||||
}
|
||||
|
||||
99
match_collector/src/cdragon_cache.ts
Normal file
99
match_collector/src/cdragon_cache.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { writeFile, mkdir } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { join, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname } from 'path'
|
||||
|
||||
// Get current directory for relative path resolution
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
// CDragon base URL for specific patch
|
||||
const CDRAGON_BASE = 'https://raw.communitydragon.org/'
|
||||
|
||||
// Assets to cache for each patch
|
||||
const CDRAGON_ASSETS = [
|
||||
{
|
||||
name: 'items.json',
|
||||
path: 'plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
||||
},
|
||||
{
|
||||
name: 'perks.json',
|
||||
path: 'plugins/rcp-be-lol-game-data/global/default/v1/perks.json'
|
||||
},
|
||||
{
|
||||
name: 'perkstyles.json',
|
||||
path: 'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json'
|
||||
},
|
||||
{
|
||||
name: 'summoner-spells.json',
|
||||
path: 'plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json'
|
||||
},
|
||||
{
|
||||
name: 'champion-summary.json',
|
||||
path: 'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* Download CDragon assets for a specific patch.
|
||||
* This caches game data locally for faster access.
|
||||
*/
|
||||
async function downloadCDragonAssets(patch: string) {
|
||||
// Convert patch format for CDragon: "16.4" -> "16.4" (already in correct format)
|
||||
// CDragon uses patch format without the last minor version
|
||||
const cdragonPatch = patch
|
||||
console.log(`\n=== Downloading CDragon assets for patch ${cdragonPatch} ===`)
|
||||
|
||||
// Get cache directory from environment or use default
|
||||
// In development, use a local directory relative to project root; in production (Docker), use /cdragon
|
||||
const defaultCacheDir =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? resolve(__dirname, '../../dev/data/cdragon')
|
||||
: '/cdragon'
|
||||
const cacheDir = process.env.CDRAGON_CACHE_DIR || defaultCacheDir
|
||||
const patchDir = join(cacheDir, cdragonPatch)
|
||||
|
||||
// Create patch directory if it doesn't exist
|
||||
if (!existsSync(patchDir)) {
|
||||
await mkdir(patchDir, { recursive: true })
|
||||
console.log(`Created directory: ${patchDir}`)
|
||||
}
|
||||
|
||||
// Download each asset
|
||||
for (const asset of CDRAGON_ASSETS) {
|
||||
const url = `${CDRAGON_BASE}${cdragonPatch}/${asset.path}`
|
||||
const filePath = join(patchDir, asset.name)
|
||||
|
||||
try {
|
||||
console.log(`Downloading ${asset.name}...`)
|
||||
const response = await fetch(url)
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to download ${asset.name}: ${response.status} ${response.statusText}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
await writeFile(filePath, JSON.stringify(data, null, 2))
|
||||
console.log(`Saved ${asset.name} to ${filePath}`)
|
||||
} catch (error) {
|
||||
console.error(`Error downloading ${asset.name}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a symlink or copy to 'latest' directory for easy access
|
||||
const latestDir = join(cacheDir, 'latest')
|
||||
const latestFile = join(latestDir, 'patch.txt')
|
||||
|
||||
if (!existsSync(latestDir)) {
|
||||
await mkdir(latestDir, { recursive: true })
|
||||
}
|
||||
|
||||
await writeFile(latestFile, patch)
|
||||
console.log(`Updated latest patch reference to ${patch}`)
|
||||
|
||||
console.log('CDragon assets download complete!')
|
||||
}
|
||||
|
||||
export { downloadCDragonAssets }
|
||||
@@ -6,9 +6,56 @@ import { MongoClient } from 'mongodb'
|
||||
import champion_stat from './champion_stat'
|
||||
import { Match } from './api'
|
||||
import { PLATFORMS, getPlatformBaseUrl, getRegionalBaseUrl, getRegionForPlatform } from './platform'
|
||||
import { downloadCDragonAssets } from './cdragon_cache'
|
||||
|
||||
main()
|
||||
|
||||
/**
|
||||
* Extract patch version from gameVersion string.
|
||||
* gameVersion format is like "15.1.123.4567" -> we want "15.1"
|
||||
*/
|
||||
function extractPatchFromGameVersion(gameVersion: string): string {
|
||||
const parts = gameVersion.split('.')
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]}.${parts[1]}`
|
||||
}
|
||||
return gameVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest patch from existing match collections in the database.
|
||||
* Collections are named like "15.1_EUW1", "15.2_NA1", etc.
|
||||
*/
|
||||
async function getLatestPatchFromCollections(client: MongoClient): Promise<string | null> {
|
||||
const matchesDb = client.db('matches')
|
||||
const collections = await matchesDb.listCollections().toArray()
|
||||
const collectionNames = collections.map(c => c.name)
|
||||
|
||||
// Extract unique patch versions from collection names
|
||||
const patches = new Set<string>()
|
||||
for (const name of collectionNames) {
|
||||
// Collection names are either "patch_platform" or just "patch"
|
||||
const patch = name.split('_')[0]
|
||||
if (patch && /^\d+\.\d+$/.test(patch)) {
|
||||
patches.add(patch)
|
||||
}
|
||||
}
|
||||
|
||||
if (patches.size === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Sort patches and return the latest (highest version number)
|
||||
const sortedPatches = Array.from(patches).sort((a, b) => {
|
||||
const [aMajor, aMinor] = a.split('.').map(Number)
|
||||
const [bMajor, bMinor] = b.split('.').map(Number)
|
||||
if (aMajor !== bMajor) return bMajor - aMajor
|
||||
return bMinor - aMinor
|
||||
})
|
||||
|
||||
return sortedPatches[0]
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Check if we're in development mode with pre-loaded data
|
||||
if (process.env.NODE_ENV === 'development' && process.env.USE_IMPORTED_DATA === 'true') {
|
||||
@@ -17,13 +64,10 @@ async function main() {
|
||||
return
|
||||
}
|
||||
|
||||
// Original production mode
|
||||
// Production mode: collect matches and organize by their gameVersion
|
||||
console.log('MatchCollector - Hello !')
|
||||
const client = await connectToDatabase()
|
||||
const [latestPatch, latestPatchTime] = await fetchLatestPatchDate(client)
|
||||
console.log(
|
||||
'Connected to database, latest patch ' + latestPatch + ' was epoch: ' + latestPatchTime
|
||||
)
|
||||
console.log('Connected to database')
|
||||
|
||||
console.log('Using RIOT_API_KEY: ' + api_key)
|
||||
if (api_key != null && api_key != undefined && api_key != '') {
|
||||
@@ -31,20 +75,22 @@ async function main() {
|
||||
for (const [platform, region] of Object.entries(PLATFORMS)) {
|
||||
console.log(`\n=== Processing platform: ${platform} (region: ${region}) ===`)
|
||||
|
||||
const alreadySeenGameList = await alreadySeenGames(client, latestPatch, platform)
|
||||
console.log(
|
||||
'We already have ' + alreadySeenGameList.length + ' matches for this patch/platform !'
|
||||
)
|
||||
// Get already seen games for all patches (we'll check by gameVersion when saving)
|
||||
const alreadySeenGameList = await alreadySeenGamesAllPatches(client, platform)
|
||||
console.log('We already have ' + alreadySeenGameList.length + ' matches for this platform !')
|
||||
|
||||
const challengerLeague = await fetchChallengerLeague(platform)
|
||||
console.log('ChallengerLeague: got ' + challengerLeague.entries.length + ' entries')
|
||||
|
||||
// Use 30 days ago as start time for collecting matches
|
||||
const startTime = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60
|
||||
|
||||
const gameList: string[] = []
|
||||
let i = 0
|
||||
for (const challenger of challengerLeague.entries) {
|
||||
console.log('Entry ' + i + '/' + challengerLeague.entries.length + ' ...')
|
||||
const puuid = challenger.puuid
|
||||
const challengerGameList = await summonerGameList(puuid, latestPatchTime, region)
|
||||
const challengerGameList = await summonerGameList(puuid, startTime, region)
|
||||
for (const game of challengerGameList) {
|
||||
if (!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||
gameList.push(game)
|
||||
@@ -64,14 +110,25 @@ async function main() {
|
||||
const gameMatch = await match(game, matchRegion)
|
||||
const gameTimeline = await matchTimeline(game, matchRegion)
|
||||
gameMatch.timeline = gameTimeline
|
||||
await saveMatch(client, gameMatch, latestPatch, platform)
|
||||
// Extract patch from gameVersion and save to appropriate collection
|
||||
const patch = extractPatchFromGameVersion(gameMatch.info.gameVersion)
|
||||
await saveMatch(client, gameMatch, patch, platform)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Generating stats...')
|
||||
await champion_stat.makeChampionsStats(client, latestPatch, Object.keys(PLATFORMS))
|
||||
// Get the latest patch from collections and generate stats for it
|
||||
const latestPatch = await getLatestPatchFromCollections(client)
|
||||
if (latestPatch) {
|
||||
console.log(`Generating stats for latest patch: ${latestPatch}...`)
|
||||
await champion_stat.makeChampionsStats(client, latestPatch, Object.keys(PLATFORMS))
|
||||
|
||||
// Download CDragon assets for the latest patch
|
||||
await downloadCDragonAssets(latestPatch)
|
||||
} else {
|
||||
console.log('No matches found in database, skipping stat generation')
|
||||
}
|
||||
|
||||
console.log('All done. Closing client.')
|
||||
await client.close()
|
||||
@@ -117,13 +174,6 @@ async function connectToDatabase() {
|
||||
return client
|
||||
}
|
||||
|
||||
async function fetchLatestPatchDate(client: MongoClient) {
|
||||
const database = client.db('patches')
|
||||
const patches = database.collection('patches')
|
||||
const latestPatch = await patches.find().limit(1).sort({ date: -1 }).next()
|
||||
return [latestPatch!.patch, Math.floor(latestPatch!.date.valueOf() / 1000)]
|
||||
}
|
||||
|
||||
async function fetchChallengerLeague(platform: string) {
|
||||
const queue = 'RANKED_SOLO_5x5'
|
||||
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||
@@ -138,7 +188,7 @@ async function fetchChallengerLeague(platform: string) {
|
||||
return challengerLeague
|
||||
}
|
||||
|
||||
async function summonerGameList(puuid: string, startTime: string, region: string) {
|
||||
async function summonerGameList(puuid: string, startTime: number, region: string) {
|
||||
const baseUrl = getRegionalBaseUrl(region)
|
||||
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`
|
||||
const url = `${baseUrl}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||
@@ -174,18 +224,31 @@ async function matchTimeline(matchId: string, region: string) {
|
||||
return timeline
|
||||
}
|
||||
|
||||
async function alreadySeenGames(client: MongoClient, latestPatch: string, platform: string) {
|
||||
const database = client.db('matches')
|
||||
const collectionName = `${latestPatch}_${platform}`
|
||||
const matches = database.collection(collectionName)
|
||||
/**
|
||||
* Get already seen games across all patches for a specific platform.
|
||||
* This is used when we don't know the patch beforehand (we get it from gameVersion).
|
||||
*/
|
||||
async function alreadySeenGamesAllPatches(client: MongoClient, platform: string) {
|
||||
const matchesDb = client.db('matches')
|
||||
const collections = await matchesDb.listCollections().toArray()
|
||||
const collectionNames = collections.map(c => c.name)
|
||||
|
||||
const alreadySeen = await matches.distinct('metadata.matchId')
|
||||
return alreadySeen
|
||||
// Find all collections for this platform (format: "patch_platform")
|
||||
const platformCollections = collectionNames.filter(name => name.endsWith(`_${platform}`))
|
||||
|
||||
const allSeen: string[] = []
|
||||
for (const collectionName of platformCollections) {
|
||||
const matches = matchesDb.collection(collectionName)
|
||||
const seen = await matches.distinct('metadata.matchId')
|
||||
allSeen.push(...seen)
|
||||
}
|
||||
|
||||
return allSeen
|
||||
}
|
||||
|
||||
async function saveMatch(client: MongoClient, match: Match, latestPatch: string, platform: string) {
|
||||
async function saveMatch(client: MongoClient, match: Match, patch: string, platform: string) {
|
||||
const database = client.db('matches')
|
||||
const collectionName = `${latestPatch}_${platform}`
|
||||
const collectionName = `${patch}_${platform}`
|
||||
const matches = database.collection(collectionName)
|
||||
await matches.insertOne(match)
|
||||
}
|
||||
@@ -198,7 +261,16 @@ async function runWithPreloadedData() {
|
||||
|
||||
const client = await connectToDatabase()
|
||||
try {
|
||||
const [latestPatch] = await fetchLatestPatchDate(client)
|
||||
// Get the latest patch from collections instead of patches database
|
||||
const latestPatch = await getLatestPatchFromCollections(client)
|
||||
|
||||
if (!latestPatch) {
|
||||
console.error('❌ No match data found in database')
|
||||
console.log('💡 Please run the data import script first:')
|
||||
console.log(' node dev/scripts/setup-db.js')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Latest patch: ${latestPatch}`)
|
||||
|
||||
// Check if we have matches for this patch (including platform-specific collections)
|
||||
@@ -235,6 +307,9 @@ async function runWithPreloadedData() {
|
||||
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||
}
|
||||
|
||||
// Download CDragon assets for the latest patch
|
||||
await downloadCDragonAssets(latestPatch)
|
||||
|
||||
console.log('🎉 All stats generated successfully!')
|
||||
console.log('🚀 Your development database is ready for frontend testing!')
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user