├── .gitignore ├── README.md ├── epicMonsterSteals.js ├── soul_stats.js ├── voidgrub_stats.js ├── overall.js └── common.js /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | 3 | # Coisas mais específicas do IDL 4 | *_idl.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pro League Stats 2 | Este repositório será usado para guardar scripts de minha autoria relacionados a estatísticas do cenário competitivo de League of Legends utilizando a API pública do [Leaguepedia](https://lol.fandom.com/wiki/Help:ACS_archive_%26_post-game_JSONs). 3 | 4 | **Dados da LPL são limitados, por exemplo não é possível saber quais dragões (apenas alma) foram feitos ou quem pegou a primeira vastilarva.** 5 | 6 | ## [Overall](overall.js) 7 | Script que retorna estatísticas gerais dos times de ligas de sua escolha. Estatísticas incluem: jogos, winrate, tempo médio de jogo, % de first blood, % de primeiro arauto, % de primeira torre, % de primeiro dragão, % de primeiro baron, média de ouro, média de kills e mortes, média de torres, etc. 8 | 9 | ## [VoidGrub Stats](voidgrub_stats.js) 10 | Script que retorna a winrate dos times, de ligas de sua escolha, quando pegam de 0 a 6 vastilarvas. 11 | 12 | ## [Soul Stats](soul_stats.js) 13 | Script que retorna a winrate das almas de dragão elemental, de ligas de sua escolha. 14 | 15 | ## [Epic Monster Steals](epicMonsterSteals.js) 16 | Script que retorna os players que mais roubaram objetivos, de ligas de sua escolha. 17 | 18 | # 19 | Se você encontrar algum problema ou tiver alguma sugestão de otimização, sinta-se livre para fazer um PR! 20 | -------------------------------------------------------------------------------- /epicMonsterSteals.js: -------------------------------------------------------------------------------- 1 | const { GetBayesGames, GetGameData } = require("./common"); 2 | 3 | const leagueNames = ["CBLOL"]; 4 | 5 | async function EntryPoint() 6 | { 7 | let statistics = {}; 8 | 9 | for(let league of leagueNames) 10 | { 11 | let bayesGames = await GetBayesGames(league, "2024"); 12 | 13 | if(bayesGames.cargoquery == undefined) 14 | break; 15 | 16 | for(let entry of bayesGames.cargoquery) 17 | { 18 | let { matchDetails, timelineDetails } = await GetGameData(entry.title); 19 | 20 | if(matchDetails == null || timelineDetails == null) 21 | continue; 22 | 23 | for(let participant of matchDetails.participants) 24 | { 25 | if(participant.challenges == undefined) 26 | continue; 27 | 28 | if(participant.challenges.epicMonsterSteals == 0) 29 | continue; 30 | 31 | if(statistics[participant.riotIdGameName] == undefined) 32 | statistics[participant.riotIdGameName] = { name: participant.riotIdGameName, epicMonsterSteals: 0 }; 33 | 34 | statistics[participant.riotIdGameName].steals += participant.challenges.epicMonsterSteals; 35 | } 36 | } 37 | } 38 | 39 | //#region 40 | 41 | statistics = Object.values(statistics); 42 | statistics.sort((a, b) => b.steals - a.steals); 43 | 44 | //#endregion 45 | 46 | console.log(statistics); 47 | } 48 | 49 | EntryPoint(); -------------------------------------------------------------------------------- /soul_stats.js: -------------------------------------------------------------------------------- 1 | const { GetBayesGames, GetGameData } = require("./common"); 2 | 3 | const leagueNames = []; 4 | 5 | async function EntryPoint() 6 | { 7 | let statistics = []; 8 | 9 | for(let league of leagueNames) 10 | { 11 | let bayesGames = await GetBayesGames(league, "2024"); 12 | 13 | if(bayesGames.cargoquery == undefined) 14 | break; 15 | 16 | for(let entry of bayesGames.cargoquery) 17 | { 18 | let { matchDetails, timelineDetails } = await GetGameData(entry.title); 19 | 20 | if(matchDetails == null || timelineDetails == null) 21 | continue; 22 | 23 | let winnerTeam = matchDetails.teams[0].win ? matchDetails.teams[0].teamId : matchDetails.teams[1].teamId 24 | 25 | let gameDragons = {blue: [], red: []} 26 | 27 | for(let frame of timelineDetails.frames) 28 | { 29 | for(let event of frame.events) 30 | { 31 | // Específico pro arquivo da LPL que eu fiz 32 | if(event.type == "ELITE_MONSTER_KILL_LPL") 33 | { 34 | statistics.push({winnerTeam, killerTeamId: event.killerTeamId, monsterSubType: event.monsterSubType}); 35 | break; 36 | } 37 | 38 | if(event.type != "ELITE_MONSTER_KILL" || event.monsterType != "DRAGON") 39 | continue; 40 | 41 | gameDragons[event.killerTeamId == 100 ? "blue" : "red"].push(event); 42 | } 43 | } 44 | 45 | if(gameDragons.blue.length >= 4 && !gameDragons.blue[3].monsterSubType.includes("ELDER")) 46 | { 47 | statistics.push({winnerTeam, killerTeamId: 100, monsterSubType: gameDragons.blue[3].monsterSubType}); 48 | } else if(gameDragons.red.length >= 4 && !gameDragons.red[3].monsterSubType.includes("ELDER")) 49 | { 50 | statistics.push({winnerTeam, killerTeamId: 200, monsterSubType: gameDragons.red[3].monsterSubType}); 51 | } 52 | } 53 | } 54 | 55 | //#region Calculo de porcentagens 56 | let stats = {}; 57 | 58 | for(let stat of statistics) 59 | { 60 | let drakeType = stat.monsterSubType.split("_")[0]; 61 | 62 | if(stats[drakeType] == undefined) 63 | { 64 | stats[drakeType] = { wins: 0, games: 0, winrate: 0 }; 65 | } 66 | 67 | stats[drakeType].wins += stat.winnerTeam == stat.killerTeamId ? 1 : 0; 68 | stats[drakeType].games += 1; 69 | } 70 | 71 | for(let stat in stats) 72 | { 73 | stats[stat].winrate = ((stats[stat].wins / stats[stat].games) * 100).toFixed(1); 74 | stats[stat].winrate = isNaN(stats[stat].winrate) ? 0 : stats[stat].winrate; 75 | stats[stat].winrate = stats[stat].winrate + "%"; 76 | } 77 | 78 | //#endregion 79 | 80 | console.log(stats); 81 | } 82 | 83 | EntryPoint(); -------------------------------------------------------------------------------- /voidgrub_stats.js: -------------------------------------------------------------------------------- 1 | const { GetBayesGames, GetGameData } = require("./common"); 2 | 3 | const leagueNames = []; 4 | 5 | async function EntryPoint() 6 | { 7 | let statistics = []; 8 | 9 | for(let league of leagueNames) 10 | { 11 | let bayesGames = await GetBayesGames(league, "2024"); 12 | 13 | if(bayesGames.cargoquery == undefined) 14 | break; 15 | 16 | for(let entry of bayesGames.cargoquery) 17 | { 18 | let { matchDetails, timelineDetails } = await GetGameData(entry.title); 19 | 20 | if(matchDetails == null || timelineDetails == null) 21 | continue; 22 | 23 | let winnerTeam = matchDetails.teams[0].win ? matchDetails.teams[0].teamId : matchDetails.teams[1].teamId 24 | let winnerGrubs = 0; 25 | let voidGrubs = []; 26 | 27 | if(matchDetails.teams[0].objectives.horde == undefined) 28 | continue; 29 | 30 | for(let team of matchDetails.teams) 31 | { 32 | if(team.teamId == winnerTeam) 33 | { 34 | winnerGrubs = team.objectives.horde.kills; 35 | } 36 | voidGrubs.push({teamId: team.teamId, kills: team.objectives.horde.kills}); 37 | } 38 | 39 | statistics.push({winnerTeam, winnerGrubs, voidGrubs}); 40 | } 41 | } 42 | 43 | //#region Calculo de porcentagens 44 | let totalGamesSixGrub = 0; 45 | let totalGamesLeastFiveGrub = 0; 46 | let totalGamesFiveGrub = 0; 47 | let totalGamesFourGrub = 0; 48 | let totalGamesThreeGrub = 0; 49 | let totalGamesTwoGrub = 0; 50 | let totalGamesOneGrub = 0; 51 | let totalGamesZeroGrub = 0; 52 | 53 | let sixGrubWinCount = 0; 54 | let leastFiveGrubWinCount = 0; 55 | let fiveGrubWinCount = 0; 56 | let fourGrubWinCount = 0; 57 | let threeGrubWinCount = 0; 58 | let twoGrubWinCount = 0; 59 | let oneGrubWinCount = 0; 60 | let zeroGrubWinCount = 0; 61 | 62 | for(let game of statistics) 63 | { 64 | if(game.winnerGrubs == 6) 65 | sixGrubWinCount += 1; 66 | 67 | if(game.winnerGrubs >= 5) 68 | leastFiveGrubWinCount += 1; 69 | 70 | if(game.winnerGrubs == 5) 71 | fiveGrubWinCount += 1; 72 | 73 | if(game.winnerGrubs == 4) 74 | fourGrubWinCount += 1; 75 | 76 | if(game.winnerGrubs == 3) 77 | threeGrubWinCount += 1; 78 | 79 | if(game.winnerGrubs == 2) 80 | twoGrubWinCount += 1; 81 | 82 | if(game.winnerGrubs == 1) 83 | oneGrubWinCount += 1; 84 | 85 | if(game.winnerGrubs == 0) 86 | zeroGrubWinCount += 1; 87 | 88 | for(let grubs of game.voidGrubs) 89 | { 90 | if(grubs.kills == 6) 91 | totalGamesSixGrub += 1; 92 | 93 | if(grubs.kills >= 5) 94 | totalGamesLeastFiveGrub += 1; 95 | 96 | if(grubs.kills == 5) 97 | totalGamesFiveGrub += 1; 98 | 99 | if(grubs.kills == 4) 100 | totalGamesFourGrub += 1; 101 | 102 | if(grubs.kills == 3) 103 | totalGamesThreeGrub += 1; 104 | 105 | if(grubs.kills == 2) 106 | totalGamesTwoGrub += 1; 107 | 108 | if(grubs.kills == 1) 109 | totalGamesOneGrub += 1; 110 | 111 | if(grubs.kills == 0) 112 | totalGamesZeroGrub += 1; 113 | } 114 | } 115 | 116 | let sixGrubWinPercent = ((sixGrubWinCount / totalGamesSixGrub) * 100).toFixed(1); 117 | let leastFiveGrubWinPercent = ((leastFiveGrubWinCount / totalGamesLeastFiveGrub) * 100).toFixed(1);; 118 | let fiveGrubWinPercent = ((fiveGrubWinCount / totalGamesFiveGrub) * 100).toFixed(1); 119 | let fourGrubWinPercent = ((fourGrubWinCount / totalGamesFourGrub) * 100).toFixed(1); 120 | let threeGrubWinPercent = ((threeGrubWinCount / totalGamesThreeGrub) * 100).toFixed(1); 121 | let twoGrubWinPercent = ((twoGrubWinCount / totalGamesTwoGrub) * 100).toFixed(1); 122 | let oneGrubWinPercent = ((oneGrubWinCount / totalGamesOneGrub) * 100).toFixed(1); 123 | let zeroGrubWinPercent = ((zeroGrubWinCount / totalGamesZeroGrub) * 100).toFixed(1); 124 | 125 | sixGrubWinPercent = isNaN(sixGrubWinPercent) ? 0.0 : sixGrubWinPercent; 126 | leastFiveGrubWinPercent = isNaN(leastFiveGrubWinPercent) ? 0.0 : leastFiveGrubWinPercent; 127 | fiveGrubWinPercent = isNaN(fiveGrubWinPercent) ? 0.0 : fiveGrubWinPercent; 128 | fourGrubWinPercent = isNaN(fourGrubWinPercent) ? 0.0 : fourGrubWinPercent; 129 | threeGrubWinPercent = isNaN(threeGrubWinPercent) ? 0.0 : threeGrubWinPercent; 130 | twoGrubWinPercent = isNaN(twoGrubWinPercent) ? 0.0 : twoGrubWinPercent; 131 | oneGrubWinPercent = isNaN(oneGrubWinPercent) ? 0.0 : oneGrubWinPercent; 132 | zeroGrubWinPercent = isNaN(zeroGrubWinPercent) ? 0.0 : zeroGrubWinPercent; 133 | 134 | //#endregion 135 | 136 | console.log(`Dados relativos as seguintes ligas: ${leagueNames} | Total de jogos: ${statistics.length}`); 137 | console.log(`pelo menos 5 vastilarvas: ocorrências ${totalGamesLeastFiveGrub} | winrate ${leastFiveGrubWinPercent}%`); 138 | console.log(`6 vastilarvas: ocorrências ${totalGamesSixGrub} | winrate ${sixGrubWinPercent}%`); 139 | console.log(`5 vastilarvas: ocorrências ${totalGamesFiveGrub} | winrate ${fiveGrubWinPercent}%`); 140 | console.log(`4 vastilarvas: ocorrências ${totalGamesFourGrub} | winrate ${fourGrubWinPercent}%`); 141 | console.log(`3 vastilarvas: ocorrências ${totalGamesThreeGrub} | winrate ${threeGrubWinPercent}%`); 142 | console.log(`2 vastilarvas: ocorrências ${totalGamesTwoGrub} | winrate ${twoGrubWinPercent}%`); 143 | console.log(`1 vastilarvas: ocorrências ${totalGamesOneGrub} | winrate ${oneGrubWinPercent}%`); 144 | console.log(`0 vastilarvas: ocorrências ${totalGamesZeroGrub} | winrate ${zeroGrubWinPercent}%`); 145 | } 146 | 147 | EntryPoint(); -------------------------------------------------------------------------------- /overall.js: -------------------------------------------------------------------------------- 1 | const { GetBayesGames, GetGameData, fmtMSS, nFormatter } = require("./common"); 2 | 3 | const leagueNames = []; 4 | 5 | async function EntryPoint() 6 | { 7 | let statistics = {}; 8 | 9 | for(let league of leagueNames) 10 | { 11 | let bayesGames = await GetBayesGames(league, "2024"); 12 | 13 | if(bayesGames.cargoquery == undefined) 14 | break; 15 | 16 | for(let entry of bayesGames.cargoquery) 17 | { 18 | let { matchDetails, timelineDetails } = await GetGameData(entry.title); 19 | 20 | if(matchDetails == null || timelineDetails == null) 21 | continue; 22 | 23 | let teams = []; 24 | let totalGold = 0; 25 | 26 | for(let participant of matchDetails.participants) 27 | { 28 | let teamCode = participant.riotIdGameName != undefined ? participant.riotIdGameName.split(" ")[0] : participant.summonerName.split(" ")[0]; 29 | 30 | let teamId = participant.teamId; 31 | 32 | if(statistics[teamCode] == undefined) 33 | { 34 | statistics[teamCode] = { games: 0, winrate: 0, gameTime: 0, "fb%": 0, "fvg%": 0, "frh%": 0, "ft%": 0, "fd%": 0, "fnash%": 0, gold: 0, kills: 0, deaths: 0, towers: 0, "c.gold": 0, "c.kills": 0, "c.towers": 0, "c.grubs": 0, "c.dragons": 0, "c.nashors": 0, "%towers > 11.5": 0, "%towers > 12.5": 0, "%dragons > 4.5": 0, "%dragons > 5.5": 0, "%nashors > 1.5" : 0, "%inhib > 1.5": 0 } 35 | } 36 | 37 | // Apenas primeira vez 38 | if(!teams.includes(teamCode)) 39 | { 40 | statistics[teamCode].games += 1; 41 | statistics[teamCode].winrate += participant.win ? 1 : 0; 42 | statistics[teamCode].gameTime += matchDetails.gameDuration; 43 | 44 | let totalTowers = 0; 45 | let totalGrubs = 0; 46 | let totalDragons = 0; 47 | let totalNashors = 0; 48 | let totalInhib = 0; 49 | 50 | for(let team of matchDetails.teams) 51 | { 52 | // Apenas um time 53 | if(team.teamId == teamId) 54 | { 55 | statistics[teamCode]["fb%"] += team.objectives.champion.first ? 1 : 0; 56 | statistics[teamCode]["fvg%"] += team.objectives.horde != undefined ? team.objectives.horde.first ? 1 : 0 : 0; 57 | statistics[teamCode]["frh%"] += team.objectives.riftHerald.first ? 1 : 0; 58 | statistics[teamCode]["ft%"] += team.objectives.tower.first ? 1 : 0; 59 | statistics[teamCode]["fd%"] += team.objectives.dragon.first ? 1 : 0; 60 | statistics[teamCode]["fnash%"] += team.objectives.baron.first ? 1 : 0; 61 | 62 | statistics[teamCode].towers += team.objectives.tower.kills; 63 | } 64 | 65 | statistics[teamCode]["c.kills"] += team.objectives.champion.kills; 66 | totalTowers += team.objectives.tower.kills; 67 | totalGrubs += team.objectives.horde != undefined ? team.objectives.horde.kills : 0; 68 | totalDragons += team.objectives.dragon.kills; 69 | totalNashors += team.objectives.baron.kills; 70 | totalInhib += team.objectives.inhibitor.kills; 71 | } 72 | 73 | statistics[teamCode]["c.towers"] += totalTowers; 74 | statistics[teamCode]["c.grubs"] += totalGrubs; 75 | statistics[teamCode]["c.dragons"] += totalDragons; 76 | statistics[teamCode]["c.nashors"] += totalNashors; 77 | 78 | statistics[teamCode]["%towers > 11.5"] += totalTowers > 11 ? 1 : 0; 79 | statistics[teamCode]["%towers > 12.5"] += totalTowers > 12 ? 1 : 0; 80 | statistics[teamCode]["%dragons > 4.5"] += totalDragons > 4 ? 1 : 0; 81 | statistics[teamCode]["%dragons > 5.5"] += totalDragons > 5 ? 1 : 0; 82 | statistics[teamCode]["%nashors > 1.5"] += totalNashors > 1 ? 1 : 0; 83 | statistics[teamCode]["%inhib > 1.5"] += totalInhib > 1 ? 1 : 0; 84 | 85 | teams.push(teamCode); 86 | } 87 | 88 | statistics[teamCode].gold += participant.goldEarned; 89 | statistics[teamCode].kills += participant.kills; 90 | statistics[teamCode].deaths += participant.deaths; 91 | 92 | totalGold += participant.goldEarned; 93 | } 94 | 95 | for(let team of teams) 96 | { 97 | statistics[team]["c.gold"] += totalGold; 98 | } 99 | } 100 | } 101 | 102 | //#region Calculo de porcentagens 103 | 104 | for(let team in statistics) 105 | { 106 | statistics[team].winrate = ((statistics[team].winrate / statistics[team].games) * 100).toFixed(1) + "%"; 107 | statistics[team].gameTime = fmtMSS((statistics[team].gameTime / statistics[team].games).toFixed(0)); 108 | 109 | statistics[team]["fb%"] = ((statistics[team]["fb%"] / statistics[team].games) * 100).toFixed(1) + "%"; 110 | statistics[team]["fvg%"] = ((statistics[team]["fvg%"] / statistics[team].games) * 100).toFixed(1) + "%"; 111 | statistics[team]["frh%"] = ((statistics[team]["frh%"] / statistics[team].games) * 100).toFixed(1) + "%"; 112 | statistics[team]["ft%"] = ((statistics[team]["ft%"] / statistics[team].games) * 100).toFixed(1) + "%"; 113 | statistics[team]["fd%"] = ((statistics[team]["fd%"] / statistics[team].games) * 100).toFixed(1) + "%"; 114 | statistics[team]["fnash%"] = ((statistics[team]["fnash%"] / statistics[team].games) * 100).toFixed(1) + "%"; 115 | 116 | statistics[team].gold = nFormatter(statistics[team].gold / statistics[team].games, 2); 117 | statistics[team].kills = parseFloat((statistics[team].kills / statistics[team].games).toFixed(2)); 118 | statistics[team].deaths = parseFloat((statistics[team].deaths / statistics[team].games).toFixed(2)); 119 | statistics[team].towers = parseFloat((statistics[team].towers / statistics[team].games).toFixed(2)); 120 | 121 | statistics[team]["c.gold"] = nFormatter(statistics[team]["c.gold"] / statistics[team].games, 2); 122 | statistics[team]["c.kills"] = parseFloat((statistics[team]["c.kills"] / statistics[team].games).toFixed(2)); 123 | statistics[team]["c.towers"] = parseFloat((statistics[team]["c.towers"] / statistics[team].games).toFixed(2)); 124 | statistics[team]["c.grubs"] = parseFloat((statistics[team]["c.grubs"] / statistics[team].games).toFixed(2)); 125 | statistics[team]["c.dragons"] = parseFloat((statistics[team]["c.dragons"] / statistics[team].games).toFixed(2)); 126 | statistics[team]["c.nashors"] = parseFloat((statistics[team]["c.nashors"] / statistics[team].games).toFixed(2)); 127 | 128 | statistics[team]["%towers > 11.5"] = ((statistics[team]["%towers > 11.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 129 | statistics[team]["%towers > 12.5"] = ((statistics[team]["%towers > 12.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 130 | statistics[team]["%dragons > 4.5"] = ((statistics[team]["%dragons > 4.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 131 | statistics[team]["%dragons > 5.5"] = ((statistics[team]["%dragons > 5.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 132 | statistics[team]["%nashors > 1.5"] = ((statistics[team]["%nashors > 1.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 133 | statistics[team]["%inhib > 1.5"] = ((statistics[team]["%inhib > 1.5"] / statistics[team].games) * 100).toFixed(1) + "%"; 134 | } 135 | 136 | //#endregion 137 | 138 | console.log(statistics); 139 | } 140 | 141 | EntryPoint(); -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | String.prototype.format = function () { 2 | var args = arguments; 3 | return this.replace(/{([0-9]+)}/g, function (match, index) { 4 | return typeof args[index] == 'undefined' ? match : args[index]; 5 | }); 6 | } 7 | 8 | const bayesSearchAPI = "https://lol.fandom.com/api.php?action=cargoquery&format=json&limit=max&tables=MatchScheduleGame%3DMSG%2C%20MatchSchedule%3DMS&fields=RiotPlatformGameId%2C%20Blue%2C%20Red%2C%20QQ%2C%20MSG.N_GameInMatch%3DGameNum%2C%20DateTime_UTC&where={0}&join_on=MSG.MatchId%3DMS.MatchId&order_by=MS.DateTime_UTC%20ASC"; 9 | const bayesGameAPI = "https://lol.fandom.com/api.php?action=query&format=json&prop=revisions&titles=V5%20data%3A{0}%7CV5%20data%3A{0}%2FTimeline&rvprop=content&rvslots=main"; 10 | const lplGameAPI = "https://open.tjstats.com/match-auth-app/open/v1/compound/matchDetail?matchId={0}"; 11 | 12 | async function GetBayesGamesDEPRECATED(leagueName, year, excludeList) 13 | { 14 | let leaguepediaLeagueName = leagueName.toUpperCase(); 15 | let query = ""; 16 | let idNotNullFlag = leagueName != "LPL" && leagueName != "LDL"; // Forma temporária de melhorar a Query de jogos que não são da LPL/LDL 17 | 18 | switch(leagueName) 19 | { 20 | case "WORLDS": 21 | query = `${leaguepediaLeagueName} ${year}%`; 22 | break; 23 | case "MSI": 24 | case "Mid-Season Invitational": 25 | query = `${year} Mid-Season Invitational%`; 26 | break; 27 | case "NACL": 28 | case "North American Challengers League": 29 | query = `North American Challengers League/${year} Season/%`; 30 | break; 31 | default: 32 | query = `${leaguepediaLeagueName}/${year}%` 33 | break; 34 | } 35 | 36 | // query = query.format(leaguepediaLeagueName, year); 37 | 38 | let where = `MSG.OverviewPage LIKE '${query}'`; 39 | 40 | if(excludeList != undefined) 41 | { 42 | if(Array.isArray(excludeList)) 43 | { 44 | for(let exclude of excludeList) 45 | { 46 | where += ` AND MSG.OverviewPage NOT LIKE '%${exclude}%'`; 47 | } 48 | } else if(excludeList != "") { 49 | where += ` AND MSG.OverviewPage NOT LIKE '%${excludeList}%'`; 50 | } 51 | } 52 | 53 | if(idNotNullFlag) 54 | { 55 | where += " AND MSG.RiotPlatformGameId != 1"; 56 | } 57 | 58 | return await fetch(bayesSearchAPI.format(where), {}).then(resp => resp.json()); 59 | } 60 | 61 | async function GetBayesGames(leagueNames, year, excludeList) 62 | { 63 | let where = "("; 64 | 65 | let leagues = leagueNames; 66 | if(!Array.isArray(leagues)) 67 | { 68 | leagues = [leagues]; 69 | } 70 | 71 | for(let leagueName of leagues) 72 | { 73 | if(where != "(") 74 | where += " OR "; 75 | 76 | let leaguepediaLeagueName = leagueName.toUpperCase(); 77 | let query = ""; 78 | let idNotNullFlag = leagueName != "LPL" && leagueName != "LDL"; // Forma temporária de melhorar a Query de jogos que não são da LPL/LDL 79 | 80 | switch(leagueName) 81 | { 82 | case "WORLDS": 83 | query = `${leaguepediaLeagueName} ${year}%`; 84 | break; 85 | case "MSI": 86 | case "Mid-Season Invitational": 87 | query = `${year} Mid-Season Invitational%`; 88 | break; 89 | case "NACL": 90 | case "North American Challengers League": 91 | query = `North American Challengers League/${year} Season/%`; 92 | break; 93 | default: 94 | query = `${leaguepediaLeagueName}/${year}%` 95 | break; 96 | } 97 | 98 | // query = query.format(leaguepediaLeagueName, year); 99 | 100 | where += `(MSG.OverviewPage LIKE '${query}'`; 101 | 102 | if(idNotNullFlag) 103 | { 104 | where += " AND MSG.RiotPlatformGameId != 1"; 105 | } else { 106 | where += " AND MS.QQ != 1"; 107 | } 108 | 109 | where += ")"; 110 | } 111 | 112 | where += ")"; 113 | 114 | if(excludeList != undefined) 115 | { 116 | if(Array.isArray(excludeList)) 117 | { 118 | for(let exclude of excludeList) 119 | { 120 | where += ` AND MSG.OverviewPage NOT LIKE '%${exclude}%'`; 121 | } 122 | } else if(excludeList != "") { 123 | where += ` AND MSG.OverviewPage NOT LIKE '%${excludeList}%'`; 124 | } 125 | } 126 | 127 | let response = await fetch(bayesSearchAPI.format(where), {}).then(resp => resp.json()); 128 | 129 | if(response.cargoquery.length == response.limits.cargoquery) 130 | { 131 | let offset = response.cargoquery.length; 132 | 133 | while(true) 134 | { 135 | let loopResponse = await fetch(bayesSearchAPI.format(where) + `&offset=${offset}`, {}).then(resp => resp.json()); 136 | 137 | response.limits.cargoquery += loopResponse.limits.cargoquery; 138 | response.cargoquery = response.cargoquery.concat(loopResponse.cargoquery); 139 | 140 | if(loopResponse.cargoquery.length < loopResponse.limits.cargoquery) 141 | break; 142 | 143 | offset += loopResponse.cargoquery.length; 144 | } 145 | } 146 | 147 | return response; 148 | } 149 | 150 | async function GetQqGame(matchId, gameNum) 151 | { 152 | let matchDetails = { gameCreation: 0, gameDuration: 0, gameEndTimestamp: 0, gameMode: "CLASSIC", gameName: "", gameType: "CUSTOM_GAME", mapId: 11, participants: [], teams: [] }; 153 | let timelineDetails = {frameInterval: 60000, frames: []}; 154 | 155 | let qqGame = await GetCacheOrFetchGame(matchId); 156 | 157 | if(!qqGame.success) 158 | return { matchDetails: null }; 159 | 160 | let currentQqGame = qqGame.data.matchInfos[gameNum]; 161 | 162 | if(currentQqGame == undefined) 163 | return { matchDetails: null }; 164 | 165 | // Infos da partida 166 | matchDetails.gameCreation = new Date(currentQqGame.matchStartTime).getTime(); 167 | matchDetails.gameDuration = currentQqGame.gameTime; 168 | matchDetails.gameEndTimestamp = new Date(currentQqGame.matchEndTime).getTime(); 169 | matchDetails.gameName = qqGame.data.matchName; 170 | 171 | // Sigla do time pra arrumar o nick dos jogadores, e colocar na ordem dos times tbm 172 | let blueTeamName = currentQqGame.blueTeam == qqGame.data.teamAId ? qqGame.data.teamAName : qqGame.data.teamBName; 173 | let redTeamName = blueTeamName == qqGame.data.teamAName ? qqGame.data.teamBName : qqGame.data.teamAName; 174 | 175 | let teamsName = [blueTeamName, redTeamName]; 176 | 177 | // Players na ordem de time; blue 0-10 red 178 | let blueTeamIndex = currentQqGame.teamInfos[0].teamId == qqGame.data.teamAId ? 0 : 1; 179 | let redTeamIndex = blueTeamIndex == 0 ? 1 : 0; 180 | 181 | let teamsIndex = [blueTeamIndex, redTeamIndex]; 182 | let teamsWin = [currentQqGame.matchWin == currentQqGame.blueTeam, currentQqGame.matchWin != currentQqGame.blueTeam]; 183 | 184 | for(let teamIndex of teamsIndex) 185 | { 186 | let isBlueTeam = teamIndex == blueTeamIndex; 187 | let didFirstBlood = false; 188 | 189 | if(currentQqGame.teamInfos[teamIndex].dragonSpirit == true) 190 | { 191 | // Pra não embolar esse drake único com os normais da API oficial, esse se chama ELITE_MONSTER_KILL_LPL pra diferenciar 192 | let drakeFrame = { assistingParticipantIds: [], bounty: 0, killerId: 0, killerTeamId: isBlueTeam ? 100 : 200, monsterSubType: `${currentQqGame.teamInfos[teamIndex].dragonSpiritType.toUpperCase()}_DRAGON`, monsterType: "DRAGON", position: {x: 9866, y: 4414}, timestamp: 0, type: "ELITE_MONSTER_KILL_LPL" }; 193 | 194 | timelineDetails.frames.push({ events:[drakeFrame] }); 195 | } 196 | 197 | for(let player of currentQqGame.teamInfos[teamIndex].playerInfos) 198 | { 199 | // TBD: runas 200 | let playerData = { assists: player.battleDetail.assist, champLevel: player.otherDetail.level, championId: player.heroId, championName: "", damageDealtToBuildings: 0, damageDealtToObjectives: 0, damageDealtToTurrets: 0, damageSelfMitigated: 0, deaths: player.battleDetail.death, goldEarned: player.otherDetail.golds, goldSpent: player.otherDetail.spentGold, iteam0: 0, item1: 0, item2: 0, item3: 0, item4: 0, item5: 0, item6: player.trinketItem.itemId, kills: player.battleDetail.kills, largestCriticalStrike: player.damageDetail.highestCritDamage, largestKillingSpree: player.battleDetail.highestKillStreak, largestMultiKill: player.battleDetail.highestMultiKill, lane: player.playerLocation, magicDamageDealt: player.damageDetail.totalMagicalDamage, magicDamageDealtToChampions: player.damageDetail.heroMagicalDamage, magicDamageTaken: player.DamageTakenDetail.magicalDamageTaken, neutralMinionsKilled: player.otherDetail.totalNeutralMinKilled, participantId: matchDetails.participants.length + 1, perks: {}, physicalDamageDealt: player.damageDetail.totalPhysicalDamage, physicalDamageDealtToChampions: player.damageDetail.heroPhysicalDamage, physicalDamageTaken: player.DamageTakenDetail.physicalDamageTaken, riotIdGameName: "", role: player.playerLocation, spell1Id: player.spell1id, spell2Id: player.spell2id, teamId: isBlueTeam ? 100 : 200, totalAllyJungleMinionsKilled: player.otherDetail.totalMinKilledYourJungle, totalDamageDealt: player.damageDetail.totalDamage, totalDamageDealtToChampions: player.damageDetail.heroDamage, totalDamageShieldedOnTeammates: 0, totalDamageTaken: player.DamageTakenDetail.damageTaken, totalEnemyJungleMinionsKilled: player.otherDetail.totalMinKilledEnemyJungle, totalHeal: 0, totalHealsOnTeammates: 0, totalMinionsKilled: player.minionKilled, totalTimeCCDealt: 0, totalTimeSpentDead: player.otherDetail.deathTime, totalUnitsHealed: 0, trueDamageDealt: player.damageDetail.totalTrueDamage, trueDamageDealtToChampions: player.damageDetail.heroTrueDamage, trueDamageTaken: player.DamageTakenDetail.trueDamageTaken, turretKills: player.otherDetail.turretAmount, turretTakedowns: 0, turretsLost: 0, visionScore: player.visionDetail.visionScore, visionWardsBoughtInGame: player.visionDetail.controlWardPurchased, wardsKilled: player.visionDetail.wardKilled, wardsPlaced: player.visionDetail.wardPlaced, win: teamsWin[teamIndex] }; 201 | 202 | playerData.championName = await GetChampionName(player.heroId); 203 | 204 | for(let itemIndex in player.items) 205 | { 206 | playerData["item" + itemIndex] = player.items[itemIndex].itemId; 207 | } 208 | 209 | playerData.riotIdGameName = teamsName[teamIndex] + " " + player.playerName.replace(teamsName[teamIndex], ""); 210 | 211 | if(player.otherDetail.firstBlood) 212 | { 213 | didFirstBlood = true; 214 | } 215 | 216 | matchDetails.participants.push(playerData); 217 | } 218 | 219 | let teamData = { bans: [], objectives: { baron: {first: false, kills: currentQqGame.teamInfos[teamIndex].baronAmount}, champion: {first: didFirstBlood, kills: currentQqGame.teamInfos[teamIndex].kills}, dragon: {first: currentQqGame.teamInfos[teamIndex].isFirstDragon, kills: currentQqGame.teamInfos[teamIndex].dragonAmount + currentQqGame.teamInfos[teamIndex].elderDragonAmount }, inhibitor: {first: currentQqGame.teamInfos[teamIndex].isFirstInhibitor, kills: currentQqGame.teamInfos[teamIndex].inhibitKills}, riftHerald: {first: currentQqGame.teamInfos[teamIndex].isFirstRiftHerald, kills: currentQqGame.teamInfos[teamIndex].isFirstRiftHerald ? 1 : 0}, tower: {first: currentQqGame.teamInfos[teamIndex].isFirstTurret, kills: currentQqGame.teamInfos[teamIndex].turretAmount}, horde: {first: false, kills: currentQqGame.teamInfos[teamIndex].voidGrubAmount != undefined ? currentQqGame.teamInfos[teamIndex].voidGrubAmount : 0} }, teamId: isBlueTeam ? 100 : 200, win: teamsWin[teamIndex] }; 220 | 221 | for(let banIndex in currentQqGame.teamInfos[teamIndex].banHeroList) 222 | { 223 | let pickTurn = 1; 224 | switch(banIndex) 225 | { 226 | case 0: 227 | pickTurn = isBlueTeam ? 1 : 2; 228 | break; 229 | case 1: 230 | pickTurn = isBlueTeam ? 3 : 4; 231 | break; 232 | case 2: 233 | pickTurn = isBlueTeam ? 5 : 6; 234 | break; 235 | case 3: 236 | pickTurn = isBlueTeam ? 2 : 1; 237 | break; 238 | case 4: 239 | pickTurn = isBlueTeam ? 4 : 3; 240 | break; 241 | } 242 | 243 | teamData.bans.push({pickTurn, championId: currentQqGame.teamInfos[teamIndex].banHeroList[banIndex]}); 244 | } 245 | 246 | matchDetails.teams.push(teamData); 247 | } 248 | 249 | return { matchDetails, timelineDetails }; 250 | } 251 | 252 | async function GetGameData(entry) 253 | { 254 | if(entry.RiotPlatformGameId == null) 255 | { 256 | if(entry.QQ == null) 257 | { 258 | return { matchDetails: null, timelineDetails: null }; 259 | } 260 | 261 | return await GetQqGame(entry.QQ, Number(entry.GameNum) - 1); 262 | } 263 | 264 | return await GetBayersGame(entry.RiotPlatformGameId); 265 | } 266 | 267 | async function GetBayersGame(RiotPlatformGameId) 268 | { 269 | let matchDetails = null; 270 | let timelineDetails = null; 271 | 272 | let bayesGame = await GetCacheOrFetchGame(RiotPlatformGameId); 273 | 274 | for(let pageNum in bayesGame.query.pages) 275 | { 276 | let page = bayesGame.query.pages[pageNum]; 277 | 278 | if(page.title.includes("Timeline")) 279 | { 280 | timelineDetails = page.revisions != undefined ? JSON.parse(page.revisions[0].slots.main["*"]) : null; 281 | continue; 282 | } 283 | 284 | matchDetails = page.revisions != undefined ? JSON.parse(page.revisions[0].slots.main["*"]) : null; 285 | } 286 | 287 | return { matchDetails, timelineDetails}; 288 | } 289 | 290 | async function GetCacheOrFetchGame(RiotGameID) 291 | { 292 | const fs = require('fs').promises; 293 | 294 | try { 295 | let fileData = await fs.readFile(`.cache/${RiotGameID}.json`); 296 | 297 | return JSON.parse(fileData); 298 | } catch { 299 | let isLPL = !isNaN(RiotGameID); 300 | let cache = false; 301 | 302 | let json = !isLPL ? await fetch(bayesGameAPI.format(RiotGameID), {}).then(resp => resp.json()) : await fetch(lplGameAPI.format(RiotGameID), {headers:{"Authorization": "7935be4c41d8760a28c05581a7b1f570"}}).then(resp => resp.json()); 303 | 304 | // Só salvar se a série da LPL tiver acabado / Só salvar o jogo do leaguepedia se tiver os dois json 305 | if(isLPL) 306 | { 307 | cache = json.success == true && json.data.matchStatus == 2; 308 | } else { 309 | let pageNums = Object.keys(json.query.pages); 310 | 311 | cache = pageNums.length == 2 && Number(pageNums[0]) >= 0 && Number(pageNums[1]) >= 0; 312 | } 313 | 314 | if(cache) 315 | { 316 | await fs.writeFile(`.cache/${RiotGameID}.json`, JSON.stringify(json), 'utf8'); 317 | } 318 | 319 | return json; 320 | } 321 | } 322 | 323 | let championsJson = {}; 324 | 325 | async function GetChampionName(id, lang = "en_US") 326 | { 327 | if(championsJson[lang] == null) 328 | { 329 | championsJson[lang] = await fetch(`http://ddragon.leagueoflegends.com/cdn/14.4.1/data/${lang}/champion.json`, {}).then(resp => resp.json()); 330 | } 331 | 332 | for(let champion in championsJson[lang].data) 333 | { 334 | if(parseInt(championsJson[lang].data[champion].key) == id) 335 | return championsJson[lang].data[champion].id; 336 | } 337 | } 338 | 339 | async function GetChampionNameById(id, lang = "en_US") 340 | { 341 | if(championsJson[lang] == null) 342 | { 343 | championsJson[lang] = await fetch(`http://ddragon.leagueoflegends.com/cdn/14.4.1/data/${lang}/champion.json`, {}).then(resp => resp.json()); 344 | } 345 | 346 | for(let champion in championsJson[lang].data) 347 | { 348 | if(championsJson[lang].data[champion].id == id) 349 | return championsJson[lang].data[champion].name; 350 | } 351 | } 352 | 353 | function fmtMSS(s){return(s-(s%=60))/60+(9 0; i--) { 369 | if (Math.abs(num) >= si[i].value) { 370 | break; 371 | } 372 | } 373 | return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol; 374 | } 375 | 376 | module.exports = { GetBayesGames, GetGameData, GetBayersGame, GetQqGame, fmtMSS, nFormatter, GetChampionName, GetChampionNameById } --------------------------------------------------------------------------------