From 414eda66f25a7ac5e8b6035bb5ba75bacbb525c2 Mon Sep 17 00:00:00 2001 From: Kalane Date: Sun, 19 Sep 2021 22:43:00 +0200 Subject: [PATCH] feat: add command to load Match-v4 matches --- server-v2/ace-manifest.json | 27 ++ server-v2/app/Parsers/MatchV4Parser.ts | 246 ++++++++++++++++++ .../Jax/src/Endpoints/MatchV4Endpoint.ts | 240 +++++++++++++++++ .../Jax/src/Endpoints/MatchlistV4Endpoint.ts | 43 +++ server-v2/app/Services/Jax/src/Jax.ts | 6 + server-v2/app/Services/Jax/src/JaxRequest.ts | 12 +- server-v2/app/Services/MatchV4Service.ts | 72 +++++ server-v2/commands/LoadV4Matches.ts | 66 +++++ 8 files changed, 710 insertions(+), 2 deletions(-) create mode 100644 server-v2/app/Parsers/MatchV4Parser.ts create mode 100644 server-v2/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts create mode 100644 server-v2/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts create mode 100644 server-v2/app/Services/MatchV4Service.ts create mode 100644 server-v2/commands/LoadV4Matches.ts diff --git a/server-v2/ace-manifest.json b/server-v2/ace-manifest.json index 79eaee7..bf47dce 100644 --- a/server-v2/ace-manifest.json +++ b/server-v2/ace-manifest.json @@ -1,5 +1,32 @@ { "commands": { + "load:v4": { + "settings": { + "loadApp": true, + "stayAlive": false + }, + "commandPath": "./commands/LoadV4Matches", + "commandName": "load:v4", + "description": "Load matches for a given Summoner from the old Match-V4 endpoint", + "args": [ + { + "type": "string", + "propertyName": "summoner", + "name": "summoner", + "required": true, + "description": "Summoner name to seach" + }, + { + "type": "string", + "propertyName": "region", + "name": "region", + "required": true, + "description": "League region of the summoner" + } + ], + "aliases": [], + "flags": [] + }, "dump:rcfile": { "settings": {}, "commandPath": "@adonisjs/core/build/commands/DumpRc", diff --git a/server-v2/app/Parsers/MatchV4Parser.ts b/server-v2/app/Parsers/MatchV4Parser.ts new file mode 100644 index 0000000..a5d968b --- /dev/null +++ b/server-v2/app/Parsers/MatchV4Parser.ts @@ -0,0 +1,246 @@ +import Database from '@ioc:Adonis/Lucid/Database' +import Match from 'App/Models/Match' +import { getSeasonNumber, notEmpty, PlayerRole, queuesWithRole, supportItems } from 'App/helpers' +import CDragonService from 'App/Services/CDragonService' +import { ChampionRoles, TeamPosition } from './ParsedType' +import { V4MatchDto } from 'App/Services/Jax/src/Endpoints/MatchV4Endpoint' +import RoleIdentificationService from 'App/Services/RoleIdentificationService' +import Jax from 'App/Services/Jax' + +class MatchV4Parser { + public createMatchId(gameId: number, region: string) { + return `${region.toUpperCase()}_${gameId}` + } + + private getTeamRoles(team: PlayerRole[]) { + const teamJunglers = team.filter((p) => p.jungle && !p.support) + const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined + const teamSupports = team.filter((p) => p.support && !p.jungle) + const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined + + return RoleIdentificationService.getRoles( + CDragonService.championRoles, + team.map((p) => p.champion), + jungle, + support + ) + } + + private getMatchRoles(match: V4MatchDto) { + const blueChamps: PlayerRole[] = [] + const redChamps: PlayerRole[] = [] + + match.participants.map((p) => { + const items = [ + p.stats.item0, + p.stats.item1, + p.stats.item2, + p.stats.item3, + p.stats.item4, + p.stats.item5, + ] + const playerRole = { + champion: p.championId, + jungle: p.spell1Id === 11 || p.spell2Id === 11, + support: supportItems.some((suppItem) => items.includes(suppItem)), + } + p.teamId === 100 ? blueChamps.push(playerRole) : redChamps.push(playerRole) + }) + + return { + blue: this.getTeamRoles(blueChamps), + red: this.getTeamRoles(redChamps), + } + } + + public async parseOneMatch(match: V4MatchDto) { + // Parse + store in database + const matchId = this.createMatchId(match.gameId, match.platformId) + + if (match.participants.length !== 10) { + console.log(`Match not saved because < 10 players. Gamemode: ${match.queueId}`) + return + } + // PUUID of the 10 players + const accountRequests = match.participantIdentities + .filter((p) => p.player.accountId !== '0') + .map((p) => Jax.Summoner.accountId(p.player.currentAccountId, match.platformId.toLowerCase())) + const playerAccounts = (await Promise.all(accountRequests)).filter(notEmpty) + + if (!playerAccounts || !playerAccounts.length) { + console.log(`0 Account found from match: ${matchId}`) + return + } + + const isRemake = match.gameDuration < 300 + + // Roles + const { blue: blueRoles, red: redRoles } = this.getMatchRoles(match) + + // - 10x MatchPlayer + const matchPlayers: any[] = [] + for (const player of match.participants) { + const identity = match.participantIdentities.find( + (p) => p.participantId === player.participantId + )! + const isBot = identity.player.accountId === '0' + const account = isBot + ? null + : playerAccounts.find((p) => p.accountId === identity.player.currentAccountId) + + if (!account && !isBot) { + console.log(`Account not found ${identity.player.currentAccountId}`) + console.log(`Match ${matchId} not saved in the database.`) + return + } + + const kda = + player.stats.kills + player.stats.assists !== 0 && player.stats.deaths === 0 + ? player.stats.kills + player.stats.assists + : +( + player.stats.deaths === 0 + ? 0 + : (player.stats.kills + player.stats.assists) / player.stats.deaths + ).toFixed(2) + + const team = match.teams[0].teamId === player.teamId ? match.teams[0] : match.teams[1] + const totalKills = match.participants.reduce((prev, current) => { + if (current.teamId !== player.teamId) { + return prev + } + return prev + current.stats.kills + }, 0) + + const kp = + totalKills === 0 + ? 0 + : +(((player.stats.kills + player.stats.assists) * 100) / totalKills).toFixed(1) + + // Perks + const primaryStyle = player.stats.perkPrimaryStyle + const secondaryStyle = player.stats.perkSubStyle + const perksSelected: number[] = [] + for (let i = 0; i < 6; i++) { + perksSelected.push(player.stats[`perk${i}`]) + } + for (let i = 0; i < 3; i++) { + perksSelected.push(player.stats[`statPerk${i}`]) + } + + const originalChampionData = CDragonService.champions[player.championId] + const champRoles = originalChampionData.roles + + // Role + const teamRoles = player.teamId === 100 ? blueRoles : redRoles + const role = Object.entries(teamRoles).find( + ([, champion]) => player.championId === champion + )![0] + + matchPlayers.push({ + match_id: matchId, + participant_id: player.participantId, + summoner_id: isBot ? 'BOT' : identity.player.summonerId, + summoner_puuid: account ? account.puuid : 'BOT', + summoner_name: identity.player.summonerName, + win: team.win === 'Win' ? 1 : 0, + loss: team.win === 'Fail' ? 1 : 0, + remake: isRemake ? 1 : 0, + team: player.teamId, + team_position: queuesWithRole.includes(match.queueId) + ? TeamPosition[role] + : TeamPosition.NONE, + kills: player.stats.kills, + deaths: player.stats.deaths, + assists: player.stats.assists, + kda: kda, + kp: kp, + champ_level: player.stats.champLevel, + champion_id: player.championId, + champion_role: ChampionRoles[champRoles[0]], + double_kills: player.stats.doubleKills, + triple_kills: player.stats.tripleKills, + quadra_kills: player.stats.quadraKills, + penta_kills: player.stats.pentaKills, + baron_kills: 0, + dragon_kills: 0, + turret_kills: player.stats.turretKills, + vision_score: player.stats.visionScore, + gold: player.stats.goldEarned, + summoner1_id: player.spell1Id, + summoner2_id: player.spell2Id, + item0: player.stats.item0, + item1: player.stats.item1, + item2: player.stats.item2, + item3: player.stats.item3, + item4: player.stats.item4, + item5: player.stats.item5, + item6: player.stats.item6, + damage_dealt_objectives: player.stats.damageDealtToObjectives, + damage_dealt_champions: player.stats.totalDamageDealtToChampions, + damage_taken: player.stats.totalDamageTaken, + heal: player.stats.totalHeal, + minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled, + critical_strike: player.stats.largestCriticalStrike, + killing_spree: player.stats.killingSprees, + time_spent_living: player.stats.longestTimeSpentLiving, + perks_primary_style: primaryStyle ?? 8100, + perks_secondary_style: secondaryStyle ?? 8000, + perks_selected: perksSelected, + }) + } + await Database.table('match_players').multiInsert(matchPlayers) + + // - 1x Match + const parsedMatch = await Match.create({ + id: matchId, + gameId: match.gameId, + map: match.mapId, + gamemode: match.queueId, + date: match.gameCreation, + region: match.platformId.toLowerCase(), + result: match.teams[0].win === 'Win' ? match.teams[0].teamId : match.teams[1].teamId, + season: getSeasonNumber(match.gameCreation), + gameDuration: match.gameDuration, + }) + + // - 2x MatchTeam : Red and Blue + for (const team of match.teams) { + let result = team.win === 'Win' ? 'Win' : 'Fail' + if (isRemake) { + result = 'Remake' + } + await parsedMatch.related('teams').create({ + matchId: matchId, + color: team.teamId, + result: result, + barons: team.baronKills, + dragons: team.dragonKills, + inhibitors: team.inhibitorKills, + riftHeralds: team.riftHeraldKills, + towers: team.towerKills, + bans: team.bans.length ? team.bans.map((ban) => ban.championId) : undefined, + banOrders: team.bans.length ? team.bans.map((ban) => ban.pickTurn) : undefined, + }) + } + + // Load Match relations + await parsedMatch.load((loader) => { + loader.load('teams').load('players') + }) + return parsedMatch + } + + public async parse(matches: V4MatchDto[]) { + // Loop on all matches and call .parseOneMatch on it + const parsedMatches: Match[] = [] + for (const match of matches) { + const parsed = await this.parseOneMatch(match) + if (parsed) { + parsedMatches.push(parsed) + } + } + return parsedMatches.length + } +} + +export default new MatchV4Parser() diff --git a/server-v2/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts b/server-v2/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts new file mode 100644 index 0000000..78dff27 --- /dev/null +++ b/server-v2/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts @@ -0,0 +1,240 @@ +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +export interface V4MatchDto { + gameId: number + participantIdentities: V4ParticipantIdentityDto[] + queueId: number + gameType: string + gameDuration: number + teams: V4TeamStatsDto[] + platformId: string + gameCreation: number + seasonId: number + gameVersion: string + mapId: number + gameMode: string + participants: V4ParticipantDto[] +} + +export interface V4ParticipantIdentityDto { + participantId: number + player: V4PlayerDto +} + +export interface V4PlayerDto { + profileIcon: number + accountId: string + matchHistoryUri: string + currentAccountId: string + currentPlatformId: string + summonerName: string + summonerId: string + platformId: string +} + +export interface V4TeamStatsDto { + towerKills: number + riftHeraldKills: number + firstBlood: boolean + inhibitorKills: number + bans: V4TeamBansDto[] + firstBaron: boolean + firstDragon: boolean + dominionVictoryScore: number + dragonKills: number + baronKills: number + firstInhibitor: boolean + firstTower: boolean + vilemawKills: number + firstRiftHerald: boolean + teamId: number // 100 for blue side. 200 for red side. + win: string +} + +export interface V4TeamBansDto { + championId: number + pickTurn: number +} + +export interface V4ParticipantDto { + participantId: number + championId: number + runes: V4RuneDto[] + stats: V4ParticipantStatsDto + teamId: number + timeline: V4ParticipantTimelineDto + spell1Id: number + spell2Id: number + highestAchievedSeasonTier?: + | 'CHALLENGER' + | 'MASTER' + | 'DIAMOND' + | 'PLATINUM' + | 'GOLD' + | 'SILVER' + | 'BRONZE' + | 'UNRANKED' + masteries: V4MasteryDto[] +} + +export interface V4RuneDto { + runeId: number + rank: number +} + +export interface V4ParticipantStatsDto { + item0: number + item2: number + totalUnitsHealed: number + item1: number + largestMultiKill: number + goldEarned: number + firstInhibitorKill: boolean + physicalDamageTaken: number + nodeNeutralizeAssist: number + totalPlayerScore: number + champLevel: number + damageDealtToObjectives: number + totalDamageTaken: number + neutralMinionsKilled: number + deaths: number + tripleKills: number + magicDamageDealtToChampions: number + wardsKilled: number + pentaKills: number + damageSelfMitigated: number + largestCriticalStrike: number + nodeNeutralize: number + totalTimeCrowdControlDealt: number + firstTowerKill: boolean + magicDamageDealt: number + totalScoreRank: number + nodeCapture: number + wardsPlaced: number + totalDamageDealt: number + timeCCingOthers: number + magicalDamageTaken: number + largestKillingSpree: number + totalDamageDealtToChampions: number + physicalDamageDealtToChampions: number + neutralMinionsKilledTeamJungle: number + totalMinionsKilled: number + firstInhibitorAssist: boolean + visionWardsBoughtInGame: number + objectivePlayerScore: number + kills: number + firstTowerAssist: boolean + combatPlayerScore: number + inhibitorKills: number + turretKills: number + participantId: number + trueDamageTaken: number + firstBloodAssist: boolean + nodeCaptureAssist: number + assists: number + teamObjective: number + altarsNeutralized: number + goldSpent: number + damageDealtToTurrets: number + altarsCaptured: number + win: boolean + totalHeal: number + unrealKills: number + visionScore: number + physicalDamageDealt: number + firstBloodKill: boolean + longestTimeSpentLiving: number + killingSprees: number + sightWardsBoughtInGame: number + trueDamageDealtToChampions: number + neutralMinionsKilledEnemyJungle: number + doubleKills: number + trueDamageDealt: number + quadraKills: number + item4: number + item3: number + item6: number + item5: number + playerScore0: number + playerScore1: number + playerScore2: number + playerScore3: number + playerScore4: number + playerScore5: number + playerScore6: number + playerScore7: number + playerScore8: number + playerScore9: number + perk0: number + perk0Var1: number + perk0Var2: number + perk0Var3: number + perk1: number + perk1Var1: number + perk1Var2: number + perk1Var3: number + perk2: number + perk2Var1: number + perk2Var2: number + perk2Var3: number + perk3: number + perk3Var1: number + perk3Var2: number + perk3Var3: number + perk4: number + perk4Var1: number + perk4Var2: number + perk4Var3: number + perk5: number + perk5Var1: number + perk5Var2: number + perk5Var3: number + perkPrimaryStyle: number + perkSubStyle: number + statPerk0: number + statPerk1: number + statPerk2: number +} + +export interface V4ParticipantTimelineDto { + participantId: number + csDiffPerMinDeltas: { [index: string]: number } + damageTakenPerMinDeltas: { [index: string]: number } + role: 'DUO' | 'NONE' | 'SOLO' | 'DUO_CARRY' | 'DUO_SUPPORT' + damageTakenDiffPerMinDeltas: { [index: string]: number } + xpPerMinDeltas: { [index: string]: number } + xpDiffPerMinDeltas: { [index: string]: number } + lane: 'MID' | 'MIDDLE' | 'TOP' | 'JUNGLE' | 'BOT' | 'BOTTOM' + creepsPerMinDeltas: { [index: string]: number } + goldPerMinDeltas: { [index: string]: number } +} + +export interface V4MasteryDto { + rank: number + masteryId: number +} + +export default class MatchV4Endpoint { + private config: JaxConfig + private limiter: RiotRateLimiter + + constructor(config: JaxConfig, limiter: RiotRateLimiter) { + this.config = config + this.limiter = limiter + + this.get = this.get.bind(this) + } + + public get(matchID: number, region: string): Promise { + return new JaxRequest( + region, + this.config, + `match/v4/matches/${matchID}`, + this.limiter, + 1500 + ).execute() + } +} diff --git a/server-v2/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts b/server-v2/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts new file mode 100644 index 0000000..a151a89 --- /dev/null +++ b/server-v2/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts @@ -0,0 +1,43 @@ +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +export interface V4MatchlistDto { + startIndex: number + totalGames: number + endIndex: number + matches: V4MatchReferenceDto[] +} + +export interface V4MatchReferenceDto { + gameId: number + role: string + season: number + platformId: string + champion: number + queue: number + lane: string + timestamp: number + seasonMatch?: number +} + +export default class MatchlistV4Endpoint { + private config: JaxConfig + private limiter: RiotRateLimiter + + constructor(config: JaxConfig, limiter: RiotRateLimiter) { + this.config = config + this.limiter = limiter + } + + public accountID(accountID: string, region: string, beginIndex = 0): Promise { + return new JaxRequest( + region, + this.config, + `match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`, + this.limiter, + 0 + ).execute() + } +} diff --git a/server-v2/app/Services/Jax/src/Jax.ts b/server-v2/app/Services/Jax/src/Jax.ts index ca12fa2..fc3735a 100644 --- a/server-v2/app/Services/Jax/src/Jax.ts +++ b/server-v2/app/Services/Jax/src/Jax.ts @@ -8,6 +8,8 @@ import { JaxConfig } from '../JaxConfig' // import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' import RiotRateLimiter from 'riot-ratelimiter' import { STRATEGY } from 'riot-ratelimiter/dist/RateLimiter' +import MatchV4Endpoint from './Endpoints/MatchV4Endpoint' +import MatchlistV4Endpoint from './Endpoints/MatchlistV4Endpoint' export default class Jax { public key: string @@ -15,7 +17,9 @@ export default class Jax { public config: JaxConfig public League: LeagueEndpoint public Match: MatchEndpoint + public MatchV4: MatchV4Endpoint public Matchlist: MatchlistEndpoint + public MatchlistV4: MatchlistV4Endpoint public Spectator: SpectatorEndpoint public Summoner: SummonerEndpoint public CDragon: CDragonEndpoint @@ -34,7 +38,9 @@ export default class Jax { this.League = new LeagueEndpoint(this.config, this.limiter) this.Match = new MatchEndpoint(this.config, this.limiter) + this.MatchV4 = new MatchV4Endpoint(this.config, this.limiter) this.Matchlist = new MatchlistEndpoint(this.config, this.limiter) + this.MatchlistV4 = new MatchlistV4Endpoint(this.config, this.limiter) this.Spectator = new SpectatorEndpoint(this.config, this.limiter) this.Summoner = new SummonerEndpoint(this.config, this.limiter) this.CDragon = new CDragonEndpoint(this.config) diff --git a/server-v2/app/Services/Jax/src/JaxRequest.ts b/server-v2/app/Services/Jax/src/JaxRequest.ts index 4514643..47255a1 100644 --- a/server-v2/app/Services/Jax/src/JaxRequest.ts +++ b/server-v2/app/Services/Jax/src/JaxRequest.ts @@ -56,7 +56,15 @@ export default class JaxRequest { } catch ({ statusCode, ...rest }) { this.retries-- - if (statusCode !== 500 && statusCode !== 503 && statusCode !== 504) { + console.log('JAX ERROR') + console.log(rest?.cause?.code) + + if ( + statusCode !== 500 && + statusCode !== 503 && + statusCode !== 504 && + rest?.cause?.code !== 'ETIMEDOUT' + ) { // // Don't log 404 when summoner isn't playing or the summoner doesn't exist // Or if summoner has no MatchList @@ -66,7 +74,7 @@ export default class JaxRequest { !this.endpoint.includes('match/v4/matchlists/by-account') ) { Logger.error(`URL ${url}: `) - Logger.error(`JaxRequest Error ${statusCode}: `, rest) + // Logger.error(`JaxRequest Error ${statusCode}: `, rest) } return diff --git a/server-v2/app/Services/MatchV4Service.ts b/server-v2/app/Services/MatchV4Service.ts new file mode 100644 index 0000000..e01bc3f --- /dev/null +++ b/server-v2/app/Services/MatchV4Service.ts @@ -0,0 +1,72 @@ +import Jax from './Jax' +import { getSeasonNumber, notEmpty } from 'App/helpers' +import { V4MatchReferenceDto } from './Jax/src/Endpoints/MatchlistV4Endpoint' +import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint' +import MatchV4Parser from 'App/Parsers/MatchV4Parser' +import Match from 'App/Models/Match' + +class MatchService { + private async _fetchMatchListUntil(account: SummonerDTO, region: string) { + let matchList: V4MatchReferenceDto[] = [] + let alreadyIn = false + let index = 0 + do { + let newMatchList = await Jax.MatchlistV4.accountID(account.accountId, region, index) + // Error while fetching Riot API + if (!newMatchList) { + matchList = matchList.map((m) => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + return matchList + } + matchList = [...matchList, ...newMatchList.matches] + alreadyIn = newMatchList.matches.length === 0 + // If the match is made in another region : we stop fetching + if (matchList[matchList.length - 1].platformId.toLowerCase() !== region) { + alreadyIn = true + } + index += 100 + } while (!alreadyIn) + + // Remove matches from MatchList made in another region, tutorial games + 3v3 games + const tutorialModes = [2000, 2010, 2020, 460, 470, 800, 810, 820] + matchList = matchList + .filter((m) => { + const sameRegion = m.platformId.toLowerCase() === region + const notATutorialGame = !tutorialModes.includes(m.queue) + + return sameRegion && notATutorialGame + }) + .map((m) => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + + return matchList + } + + public async updateMatchList(account: SummonerDTO, region: string) { + return this._fetchMatchListUntil(account, region) + } + + public async getMatches(region: string, matchlist: V4MatchReferenceDto[]) { + const matchesToGetFromRiot: number[] = [] + for (const match of matchlist) { + const matchSaved = await Match.query() + .where('id', MatchV4Parser.createMatchId(match.gameId, region)) + .first() + if (!matchSaved) { + matchesToGetFromRiot.push(match.gameId) + } + } + + const requests = matchesToGetFromRiot.map((gameId) => Jax.MatchV4.get(gameId, region)) + const matchesFromApi = await Promise.all(requests) + const filteredMatches = matchesFromApi.filter(notEmpty) + + return filteredMatches.length ? await MatchV4Parser.parse(filteredMatches) : 0 + } +} + +export default new MatchService() diff --git a/server-v2/commands/LoadV4Matches.ts b/server-v2/commands/LoadV4Matches.ts new file mode 100644 index 0000000..87dd2ff --- /dev/null +++ b/server-v2/commands/LoadV4Matches.ts @@ -0,0 +1,66 @@ +import { BaseCommand, args } from '@adonisjs/core/build/standalone' +import MatchV4Service from 'App/Services/MatchV4Service' +import SummonerService from 'App/Services/SummonerService' + +export default class LoadV4Matches extends BaseCommand { + /** + * Command name is used to run the command + */ + public static commandName = 'load:v4' + + /** + * Command description is displayed in the "help" output + */ + public static description = 'Load matches for a given Summoner from the old Match-V4 endpoint' + + @args.string({ description: 'Summoner name to seach' }) + public summoner: string + + @args.string({ description: 'League region of the summoner' }) + public region: string + + public static settings = { + /** + * Set the following value to true, if you want to load the application + * before running the command + */ + loadApp: true, + + /** + * Set the following value to true, if you want this command to keep running until + * you manually decide to exit the process + */ + stayAlive: false, + } + + public async run() { + this.logger.info(`Trying to find ${this.summoner} from ${this.region}`) + + // ACCOUNT + const account = await SummonerService.getAccount(this.summoner, this.region) + if (account) { + this.logger.success('League account found.') + } else { + return this.logger.error('League account not found.') + } + + // MATCHLIST + const matchListIds = await MatchV4Service.updateMatchList(account, this.region) + if (matchListIds.length) { + this.logger.success(`${matchListIds.length} matches in the matchlist.`) + } else { + return this.logger.error('Matchlist empty.') + } + + // MATCHES + const chunkSize = 10 + let savedMatches = 0 + for (let i = 0; i < matchListIds.length; i += chunkSize) { + const chunk = matchListIds.slice(i, i + chunkSize) + savedMatches += await MatchV4Service.getMatches(this.region, chunk) + this.logger.info(`${savedMatches} matches saved.`) + } + + this.logger.success(`${savedMatches} matches saved for summoner ${this.summoner}.`) + } +}