Initial commit
This commit is contained in:
1
match_collector/.gitignore
vendored
Normal file
1
match_collector/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
8
match_collector/Dockerfile
Normal file
8
match_collector/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM node:lts-alpine
|
||||
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||
WORKDIR /home/node/app
|
||||
USER node
|
||||
COPY --chown=node:node package*.json ./
|
||||
RUN npm install
|
||||
COPY --chown=node:node . .
|
||||
CMD [ "node", "index.js" ]
|
||||
105
match_collector/champion_stat.js
Normal file
105
match_collector/champion_stat.js
Normal file
@@ -0,0 +1,105 @@
|
||||
function sameArrays(array1, array2) {
|
||||
if(array1.length != array2.length) return false;
|
||||
for(let e of array1) {
|
||||
if(!array2.includes(e)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function championInfos(client, patch, championId) {
|
||||
const database = client.db("matches");
|
||||
const matches = database.collection(patch)
|
||||
const allMatches = matches.find()
|
||||
|
||||
let winningMatches = 0;
|
||||
let losingMatches = 0;
|
||||
let totalMatches = 0;
|
||||
const runes = [];
|
||||
for await (let match of allMatches) {
|
||||
totalMatches += 1;
|
||||
for(let participant of match.info.participants) {
|
||||
if(participant.championId != championId) continue;
|
||||
|
||||
if(participant.win)
|
||||
winningMatches += 1;
|
||||
else
|
||||
losingMatches += 1;
|
||||
|
||||
const primaryStyle = participant.perks.styles[0].style
|
||||
const secondaryStyle = participant.perks.styles[1].style
|
||||
const selections = []
|
||||
for(let style of participant.perks.styles) {
|
||||
for(let perk of style.selections) {
|
||||
selections.push(perk.perk)
|
||||
}
|
||||
}
|
||||
const gameRunes = {count:1, primaryStyle: primaryStyle, secondaryStyle: secondaryStyle, selections: selections};
|
||||
let addRunes = true;
|
||||
for(let rune of runes) {
|
||||
if(rune.primaryStyle == gameRunes.primaryStyle
|
||||
&& rune.secondaryStyle == gameRunes.secondaryStyle
|
||||
&& sameArrays(rune.selections, gameRunes.selections)) {
|
||||
rune.count++; addRunes = false; break;
|
||||
}
|
||||
}
|
||||
if(addRunes) runes.push(gameRunes)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter runes to keep 3 most played
|
||||
const maxes = [0, 0, 0] // 24, 2, 1 -> 18 > 1 -> 24, 2, 2 -> 18 > 2 -> 24, 24, 2
|
||||
const maxRunes = [null, null, null]
|
||||
for(let rune of runes) {
|
||||
let maxcount = 2;
|
||||
if(rune.count <= maxes[maxcount]) continue;
|
||||
|
||||
while(maxcount > 0 && rune.count > maxes[maxcount]) {
|
||||
maxes[maxcount] = maxes[maxcount - 1];
|
||||
maxRunes[maxcount] = maxRunes[maxcount - 1];
|
||||
maxcount--;
|
||||
}
|
||||
|
||||
rune.pickrate = rune.count / (winningMatches + losingMatches)
|
||||
if(rune.count <= maxes[maxcount]) maxcount++;
|
||||
maxes[maxcount] = rune.count
|
||||
maxRunes[maxcount] = rune
|
||||
}
|
||||
|
||||
return {id: championId,
|
||||
winrate:winningMatches / (winningMatches + losingMatches),
|
||||
gameCount:(winningMatches + losingMatches),
|
||||
pickrate:(winningMatches + losingMatches)/totalMatches,
|
||||
runes: maxRunes.filter((x) => x != null)
|
||||
};
|
||||
}
|
||||
|
||||
async function makeChampionStat(client, patch, championId) {
|
||||
const championInfo = await championInfos(client, patch, championId)
|
||||
const database = client.db("champions")
|
||||
const collection = database.collection(patch)
|
||||
await collection.updateOne({id: championInfo.id}, {$set: championInfo}, { upsert: true })
|
||||
}
|
||||
|
||||
async function championList() {
|
||||
const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json");
|
||||
const list = await response.json()
|
||||
return list.slice(1)
|
||||
}
|
||||
|
||||
async function makeChampionsStats(client, patch) {
|
||||
const list = await championList()
|
||||
console.log("Generating stats for " + list.length + " champions")
|
||||
let i = 0;
|
||||
for(let champion of list) {
|
||||
console.log("Entry " + i + "/" + list.length + " ...")
|
||||
await makeChampionStat(client, patch, champion.id)
|
||||
i += 1
|
||||
}
|
||||
|
||||
const database = client.db("champions")
|
||||
const collection = database.collection(patch)
|
||||
await collection.createIndex({id:1})
|
||||
}
|
||||
|
||||
module.exports = {makeChampionsStats}
|
||||
156
match_collector/index.js
Normal file
156
match_collector/index.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const base = "https://euw1.api.riotgames.com"
|
||||
const api_key = process.env.RIOT_API_KEY
|
||||
const sleep_minutes = 3
|
||||
|
||||
var champion_stat = require("./champion_stat")
|
||||
|
||||
main()
|
||||
|
||||
async function main() {
|
||||
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)
|
||||
|
||||
const alreadySeenGameList = await alreadySeenGames(client, latestPatch);
|
||||
console.log("We already have " + alreadySeenGameList.length + " matches for this patch !")
|
||||
|
||||
const challengerLeague = await fetchChallengerLeague();
|
||||
console.log("ChallengerLeague: got " + challengerLeague.entries.length + " entries");
|
||||
|
||||
const gameList = [];
|
||||
let i = 0;
|
||||
for(let challenger of challengerLeague.entries) {
|
||||
console.log("Entry " + i + "/" + challengerLeague.entries.length + " ...")
|
||||
const puuid = await summonerPuuid(challenger.summonerId);
|
||||
const challengerGameList = await summonerGameList(puuid, latestPatchTime);
|
||||
for(let game of challengerGameList) {
|
||||
if(!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||
gameList.push(game)
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
console.log("Games: got " + gameList.length + " entries");
|
||||
i = 0;
|
||||
for(let game of gameList) {
|
||||
console.log("Entry " + i + "/" + gameList.length + " ...")
|
||||
const gameMatch = await match(game)
|
||||
const gameTimeline = await matchTimeline(game)
|
||||
gameMatch.timeline = gameTimeline
|
||||
await saveMatch(client, gameMatch, latestPatch)
|
||||
i++;
|
||||
}
|
||||
|
||||
console.log("Generating stats...");
|
||||
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||
|
||||
console.log("All done. Closing client.");
|
||||
await client.close()
|
||||
}
|
||||
|
||||
async function handleRateLimit(url) {
|
||||
let response = await fetch(url)
|
||||
if(response.status == 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, sleep_minutes * 60 * 1000))
|
||||
response = handleRateLimit(url)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
function handleError(response) {
|
||||
if(!response.ok) {
|
||||
console.log("Error during fetch(" + response.url + "): STATUS " + response.status + " (" + response.statusText + ")");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectToDatabase() {
|
||||
const { MongoClient, ServerApiVersion } = require("mongodb");
|
||||
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`)
|
||||
await client.connect()
|
||||
return client
|
||||
}
|
||||
|
||||
async function fetchLatestPatchDate(client) {
|
||||
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() {
|
||||
const queue = "RANKED_SOLO_5x5"
|
||||
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const challengerLeagueResponse = await handleRateLimit(url);
|
||||
|
||||
handleError(challengerLeagueResponse)
|
||||
|
||||
const challengerLeague = await challengerLeagueResponse.json();
|
||||
return challengerLeague;
|
||||
}
|
||||
|
||||
async function summonerPuuid(summonerId) {
|
||||
const endpoint = `/lol/summoner/v4/summoners/${summonerId}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const puuidResponse = await handleRateLimit(url);
|
||||
handleError(puuidResponse)
|
||||
const puuidJson = await puuidResponse.json();
|
||||
|
||||
return puuidJson.puuid;
|
||||
}
|
||||
async function summonerGameList(puuid, startTime) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`;
|
||||
const url = `${base}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||
|
||||
const gameListResponse = await handleRateLimit(url);
|
||||
handleError(gameListResponse)
|
||||
const gameList = await gameListResponse.json();
|
||||
|
||||
return gameList;
|
||||
}
|
||||
|
||||
async function match(matchId) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const matchResponse = await handleRateLimit(url)
|
||||
handleError(matchResponse)
|
||||
const match = await matchResponse.json();
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
async function matchTimeline(matchId) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const timelineResponse = await handleRateLimit(url)
|
||||
handleError(timelineResponse)
|
||||
const timeline = await timelineResponse.json();
|
||||
|
||||
return timeline
|
||||
}
|
||||
|
||||
async function alreadySeenGames(client, latestPatch) {
|
||||
const database = client.db("matches")
|
||||
const matches = database.collection(latestPatch)
|
||||
|
||||
const alreadySeen = await matches.distinct("metadata.matchId")
|
||||
return alreadySeen
|
||||
}
|
||||
|
||||
async function saveMatch(client, match, latestPatch) {
|
||||
const database = client.db("matches")
|
||||
const matches = database.collection(latestPatch)
|
||||
await matches.insertOne(match)
|
||||
}
|
||||
163
match_collector/package-lock.json
generated
Normal file
163
match_collector/package-lock.json
generated
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"name": "stat_collector",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stat_collector",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"mongodb": "^6.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webidl-conversions": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/whatwg-url": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bson": {
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz",
|
||||
"integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/memory-pager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz",
|
||||
"integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mongodb-js/saslprep": "^1.1.5",
|
||||
"bson": "^6.7.0",
|
||||
"mongodb-connection-string-url": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-providers": "^3.188.0",
|
||||
"@mongodb-js/zstd": "^1.1.0",
|
||||
"gcp-metadata": "^5.2.0",
|
||||
"kerberos": "^2.0.1",
|
||||
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||
"snappy": "^7.2.2",
|
||||
"socks": "^2.7.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-providers": {
|
||||
"optional": true
|
||||
},
|
||||
"@mongodb-js/zstd": {
|
||||
"optional": true
|
||||
},
|
||||
"gcp-metadata": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
},
|
||||
"socks": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/whatwg-url": "^11.0.2",
|
||||
"whatwg-url": "^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"memory-pager": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^4.1.1",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
match_collector/package.json
Normal file
15
match_collector/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "match_collector",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mongodb": "^6.10.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user