diff --git a/package.json b/package.json index 6a9192a..10a12f5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "out/index.js", "scripts": { "build": "tsc", - "start": "node out/index.js" + "start": "node out/index.js", + "deploy-commands": "node out/deploy-commands.js" }, "repository": { "type": "git", diff --git a/src/bot.ts b/src/bot.ts index d303c9f..f5178cf 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,39 +1,50 @@ -import {Client, GatewayIntentBits, Message} from "discord.js" -import {PlatformId, RiotAPI, RiotAPITypes} from "@fightmegg/riot-api" -import AccountDTO = RiotAPITypes.Account.AccountDTO; -import fs from "fs"; +import {Client, Collection, Events, GatewayIntentBits, Interaction, Message} from "discord.js" +import {Player} from "./player"; +import fs from "node:fs"; +import {Command} from "./command"; +import path from "node:path"; export class UEMEloBot extends Client { - riotApi: RiotAPI - players: { - account: AccountDTO, - startElo?: Elo - } [] = [] + players: Player[] = [] fileName = "players.json" + commands: Collection = new Collection(); constructor() { - super({ - intents: [ - GatewayIntentBits.GuildMessages, - GatewayIntentBits.Guilds - ] - }); - this.on("ready", this.onReady); - this.on("messageCreate", this.messageCreate); - this.riotApi = new RiotAPI(process.env["RIOT_API_KEY"] as string); + super({intents: [GatewayIntentBits.Guilds]}); + this.once(Events.ClientReady, this.onReady); + this.on(Events.InteractionCreate, this.onInteractionCreate); this.parsePlayersFromFile(); - this.addPlayer("benjo", "tgm"); - this.addPlayer("unclebenss", "euw"); - this.addPlayer("moché", "EUw"); - // this.login().then(token => console.log(`Token used: ${token}`)); + this.loadCommands().then(async () => { + const token = await this.login(); + console.log(`Discord Token: ${token}`); + setInterval(this.updateStartElo.bind(this), 5000); + await this.addPlayer("benjo", "tgm"); + await this.addPlayer("unclebenss", "euw"); + await this.addPlayer("moché", "EUw"); + }); + } + + async onInteractionCreate(interaction: Interaction) { + if (!interaction.isChatInputCommand()) + return; + let commandName = interaction.commandName; + let command = this.commands.ensure(commandName, () => this.commands.get("help") as Command); + await command.execute(interaction); } parsePlayersFromFile(){ if (fs.existsSync(this.fileName)){ let fileContent = fs.readFileSync(this.fileName).toString(); try { - this.players = JSON.parse(fileContent); + this.players = []; + for (let obj of JSON.parse(fileContent)){ + let p = new Player(obj.account); + if (obj.hasOwnProperty("startElo")) + p.startElo = obj.startElo; + this.players.push(p); + console.log(`Parsed player: ${p}`); + } } catch (error) { console.error(`Failed to parse players: ${error}`); } @@ -44,60 +55,46 @@ export class UEMEloBot extends Client { fs.writeFileSync(this.fileName, JSON.stringify(this.players)); } + async updateStartElo(){ + for (let p of this.players){ + if (!p.startElo){ + p.startElo = await p.fetchCurrentElo(); + console.log(`Updated start elo for ${p}`); + } + } + this.savePlayersToFile(); + } + async addPlayer(gameName: string, tag: string){ - let account: AccountDTO; - try { - account = await this.riotApi.account.getByRiotId({ - region: PlatformId.EUROPE, - gameName: gameName, - tagLine: tag - }); - } catch (error) { - console.error(`Riot ID not found: ${gameName}#${tag}!`); + let player = await Player.Create(gameName, tag) as Player; + + if (player === undefined) return; - } - let fullName = `${account.gameName}#${account.tagLine}`; - if (this.players.find(p => p.account.puuid === account.puuid)){ - console.error(`${fullName} already registered!`); + if (this.players.find(p => p.account.puuid === player.account.puuid)){ + console.error(`${player} already registered!`); return; } - let entries = await this.riotApi.league.getEntriesBySummonerId({ - region: PlatformId.EUW1, - summonerId: (await this.riotApi.summoner.getByPUUID({ - region: PlatformId.EUW1, - puuid: account.puuid - })).id - }); + this.players.push(player); - let soloQ = entries.find(e => e.queueType == "RANKED_SOLO_5x5"); - if (soloQ && ((soloQ.wins + soloQ.losses) >= 5)){ - this.players.push({ - account: account, - startElo: { - tier: soloQ.tier, - rank: soloQ.rank, - points: soloQ.leaguePoints - } - }); - } else { - this.players.push({account: account}); - } - console.log(`Added ${fullName}!`); + console.log(`Added ${player}!`); this.savePlayersToFile(); } - async onReady(){ - console.log("Bot started!"); + async loadCommands(){ + const commandFiles = fs.readdirSync(path.join(__dirname, "commands")).filter(file => file.endsWith('.js')); + for (const file of commandFiles){ + const module = await import(`./commands/${file}`); + const cmd = module.default; + if (cmd instanceof Command) + this.commands.set(cmd.data.name, cmd); + } } - async messageCreate(msg: Message){ - await msg.fetch(); - if (!msg.author.bot){ - msg.channel.send(`You said ${msg.content}`); - } + async onReady(readyClient: Client){ + console.log(`Logged in as ${readyClient.user.tag}`); } stop(){ @@ -106,38 +103,4 @@ export class UEMEloBot extends Client { process.exit(0); }); } -} - -interface Elo { - tier: string, - rank: string, - points: number -} - -function eloToNumber(elo: Elo){ - let tiers: Record = { - "CHALLENGER": 2800, - "GRANDMASTER": 2800, - "MASTER": 2800, - "DIAMOND": 2400, - "EMERALD": 2000, - "PLATINUM": 1600, - "GOLD": 1200, - "SILVER": 800, - "BRONZE": 400, - "IRON": 0, - }; - - let ranks: Record = { - "I": 300, - "II": 200, - "III": 100, - "IV": 0, - }; - - let tier = elo.tier; - if (tier === "MASTER" || tier === "GRANDMASTER" || tier === "CHALLENGER") - return tiers[tier] + elo.points; - - return tiers[tier] + ranks[elo.rank] + elo.points; } \ No newline at end of file diff --git a/src/command.ts b/src/command.ts new file mode 100644 index 0000000..b9d9f45 --- /dev/null +++ b/src/command.ts @@ -0,0 +1,11 @@ +import {Interaction, SlashCommandBuilder} from "discord.js"; + +export abstract class Command { + data: SlashCommandBuilder + + protected constructor(name: string, description: string) { + this.data = new SlashCommandBuilder().setName(name).setDescription(description); + } + + abstract execute(interaction: Interaction): Promise; +} \ No newline at end of file diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..a7e41f7 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,14 @@ +import {Interaction} from "discord.js"; +import {Command} from "../command"; + +class Ping extends Command { + constructor() { + super("ping", "One simple Ping Pong :)"); + } + async execute(interaction: Interaction) { + if (interaction.isRepliable()) + await interaction.reply("Pong!"); + } +} + +export default new Ping(); \ No newline at end of file diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts new file mode 100644 index 0000000..ac3b664 --- /dev/null +++ b/src/deploy-commands.ts @@ -0,0 +1,10 @@ +import {REST, Routes} from 'discord.js'; +require("dotenv").config(); + +let token = process.env["DISCORD_TOKEN"] as string; +let clientId = process.env["CLIENT_ID"] as string; +let guildId = process.env["GUILD_ID"] as string; + +const rest = new REST().setToken(token); + +rest.put(Routes.applicationGuildCommands(clientId, guildId), {body: [require("./commands/ping").data.toJSON()]}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 41f8a5c..7b0bc5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,11 @@ +import {Command} from "./command"; + require("dotenv").config(); import {UEMEloBot} from "./bot"; import readline from "readline"; +import Ping from "./commands/ping"; + readline.createInterface({input: process.stdin}).on('line', async line => { if (line == "stop") client.stop(); diff --git a/src/player.ts b/src/player.ts new file mode 100644 index 0000000..7d3d70f --- /dev/null +++ b/src/player.ts @@ -0,0 +1,88 @@ +import {PlatformId, RiotAPI, RiotAPITypes} from "@fightmegg/riot-api" +import AccountDTO = RiotAPITypes.Account.AccountDTO; + +const riotAPI = new RiotAPI(process.env["RIOT_API_KEY"] as string); + +export class Player { + + account: AccountDTO + startElo?: Elo + + constructor(account: AccountDTO) { + this.account = account; + } + + async fetchCurrentElo(): Promise { + let entries = await riotAPI.league.getEntriesBySummonerId({ + region: PlatformId.EUW1, + summonerId: (await riotAPI.summoner.getByPUUID({ + region: PlatformId.EUW1, + puuid: this.account.puuid + })).id + }); + + let soloQ = entries.find(e => e.queueType == "RANKED_SOLO_5x5"); + if (soloQ && ((soloQ.wins + soloQ.losses) >= 5)){ + return { + tier: soloQ.tier, + rank: soloQ.rank, + points: soloQ.leaguePoints + }; + } + } + + toString() { + return `${this.account.gameName}#${this.account.tagLine}`; + } + + static async Create(gameName: string, tag: string) { + let account: AccountDTO; + try { + account = await riotAPI.account.getByRiotId({ + region: PlatformId.EUROPE, + gameName: gameName, + tagLine: tag + }); + } catch (error) { + console.error(`Riot ID not found: ${gameName}#${tag}!`); + return; + } + let p = new Player(account); + p.startElo = await p.fetchCurrentElo(); + return p; + } +} + +interface Elo { + tier: string, + rank: string, + points: number +} + +function eloToNumber(elo: Elo){ + let tiers: Record = { + "CHALLENGER": 2800, + "GRANDMASTER": 2800, + "MASTER": 2800, + "DIAMOND": 2400, + "EMERALD": 2000, + "PLATINUM": 1600, + "GOLD": 1200, + "SILVER": 800, + "BRONZE": 400, + "IRON": 0, + }; + + let ranks: Record = { + "I": 300, + "II": 200, + "III": 100, + "IV": 0, + }; + + let tier = elo.tier; + if (tier === "MASTER" || tier === "GRANDMASTER" || tier === "CHALLENGER") + return tiers[tier] + elo.points; + + return tiers[tier] + ranks[elo.rank] + elo.points; +} \ No newline at end of file