UNPKG

lcinterface

Version:

An easy way to interact with the league client. This module is basically a middle layer between your app and the league client

347 lines (307 loc) 8.52 kB
const FileWatcher = require("./file-watcher") const { EventEmitter } = require('events') const { exec } = require('child_process') const fetch = require("node-fetch") const path = require('path') const fs = require('fs') process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0 const sleep = ms => new Promise(r => setTimeout(r, ms)) const platform = { CURRENT: process.platform, REGEX: process.platform == "win32"? /"--install-directory=(.*?)"/:/--install-directory=(.*?)( --|\n|$)/, COMMAND: process.platform == "win32"? "wmic process where caption='LeagueClientUx.exe' get commandline": "ps x -o args | grep 'LeagueClientUx'" } class LCIConnector extends EventEmitter { constructor() { super() this.lockFilePath = false this.clientPath = false this.listening = false this.getProcessLoop = false this.fileWatcher = false this.getProcessInterval = 1000 this.checkProcessInterval = 1000 this.connected = false this.data = { protocol: "https", address: "127.0.0.1", port: -1, pid: -1, username: "riot", password: "" } } async removed() { if (this.connected) { this.connected = false this.emit("disconnect") } } async created() { this.connected = true const lockFileData = fs.readFileSync(this.lockFilePath).toString().split(":") this.data.pid = parseInt(lockFileData[1]) this.data.port = parseInt(lockFileData[2]) this.data.password = lockFileData[3] this.emit("connect", this.data) } async stop_watcher() { if (this.fileWatcher) this.fileWatcher.stop() } async watch_lockfile() { if (this.fileWatcher) return this.fileWatcher = new FileWatcher(this.lockFilePath) this.fileWatcher.on("create" || "change", () => this.created()) this.fileWatcher.on("remove", () => this.removed()) this.fileWatcher.start() } async get_path() { return new Promise( (resolve) => { exec(platform.COMMAND, (error, stdout, stderr) => { if (error || !stdout || stderr) return resolve() const processPath = stdout.match(platform.REGEX) || [] this.clientPath = processPath[1] this.lockFilePath = path.join(this.clientPath, "lockfile") resolve(processPath[1]) }) }) } async get_process() { while(!this.clientPath) { await this.get_path() await sleep(this.getProcessInterval) } return true } setGetProcessInterval(getProcessInterval) { this.getProcessInterval = getProcessInterval } async connect() { if (!["win32", "linux","darwin"].includes(platform.CURRENT)) throw new Error(`Inavlid platform: ${platform.CURRENT}`) if (!this.listening) { this.listening = true await this.get_process() this.watch_lockfile() } } disconnect() { this.listening = false this.stop_watcher() clearTimeout(this.getProcessLoop) this.removed() } } const LCIClient = { base64_auth: "", data: {}, endpoints: { user: { me: "/lol-chat/v1/me" }, game: { gameflow: "/lol-gameflow/v1/gameflow-phase", session: "/lol-gameflow/v1/session", champselect: "/lol-champ-select/v1/session", action: "/lol-champ-select/v1/session/actions" }, runes: { runes: "/lol-perks/v1/pages", spells: "/lol-champ-select/v1/session/my-selection" }, lobby: { lobby: "/lol-lobby/v2/lobby", search: "/lol-lobby/v2/lobby/matchmaking/search", partytype: "/lol-lobby/v2/lobby/partyType", position: "/lol-lobby/v2/lobby/members/localMember/position-preferences", matchaccept: "/lol-matchmaking/v1/ready-check/accept", matchdecline: "/lol-matchmaking/v1/ready-check/decline", } }, game: { gameflows: { NONE: "None", LOBBY: "Lobby", MATCHMAKING: "Matchmaking", READYCHECK: "ReadyCheck", CHAMPSELECT: "ChampSelect", INPROGRESS: "InProgress", WAITINGFORSTATS: "WaitingForStats", ENDOFGAME: "EndOfGame" }, lanes: { UNSELECTED: "UNSELECTED", TOP: "TOP", JUNGLE: "JUNGLE", MIDDLE: "MIDDLE", BOTTOM: "BOTTOM", UTILITY: "UTILITY" }, spells: { Barrier: { id: 0, key: "SummonerBarrier", name: "Barrier" }, Cleanse: { id: 1, key: "SummonerBoost", name: "Cleanse" }, Exhaust: { id: 3, key: "SummonerExhaust", name: "Exhaust" }, Flash: { id: 4, key: "SummonerFlash", name: "Flash" }, Ghost: { id: 6, key: "SummonerHaste", name: "Ghost" }, Heal: { id: 7, key: "SummonerHeal", name: "Heal" }, Smite: { id: 11, key: "SummonerSmite", name: "Smite" }, Teleport: { id: 12, key: "SummonerTeleport", name: "Teleport" }, Clarity: { id: 13, key: "SummonerMana", name: "Clarity" }, Ignite: { id: 14, key: "SummonerDot", name: "Ignite" }, Mark: { id: 32, key: "SummonerSnowball", name: "Mark" } }, queueId: { normal: { blind: 430, draft: 400 }, ranked: { solo_duo: 420, flex: 440 }, extra: { aram: 450 } }, partytype: { open: "open", closed: "closed" } }, states: { hooked: false, vcc: 0, unsafe: true }, allowUnsafeCalls: function(value) { return this.setState("unsafe", value) }, setState: function(state, value) { return this.states[state] = value }, getState: function(state) { if (state in this.states) return this.states[state] return false }, addEndpoint: function(group, name, endpoint) { if (!(name in this.endpoints[group])) return this.endpoints[group][name] = endpoint return false }, isCorrectValue: function(state, value) { if (state in this.states) return this.states[state] == value return false }, __fastvirtualCall: async function(endpoint, method, data = {}) { return await this.apicall(endpoint, method, data) }, virtualCall: async function(endpoint, method, data = {}) { this.states.vcc++ if (this.states.hooked || this.states.unsafe) return await this.apicall(endpoint, method, data) else return false }, apicall: async function(endpoint, method, data) { let fetch_body = { method, rejectUnauthorized: false, headers: { Accept: "application/json", Authorization: `Basic ${this.base64_auth}`, ...(method !== "get" && { 'Content-Type': 'application/json' }) }, ...(method !== "get" && { body: JSON.stringify(data) }) } const response = await fetch(`${this.data.protocol}://${this.data.address}:${this.data.port}${endpoint}`, fetch_body) return await response.json().then( r => r ).catch( _ => response ) }, __fasthook: function({ port, password }) { this.data = { protocol: "https", address: "127.0.0.1", port: parseInt(port) }, this.base64_auth = Buffer.from(`riot:${password}`).toString('base64') return this.setState("hooked", true) }, hook: function({ protocol, address, port, username, password }) { if (!this.isCorrectValue("hooked", true)) { this.data = { protocol, address, port: parseInt(port), username, password } this.base64_auth = Buffer.from(`${username}:${password}`).toString('base64') return this.setState("hooked", true) } return false }, __fastunhook: function() { this.data = {} this.base64_auth = "" return this.setState("hooked", false) }, unhook: function() { if (this.isCorrectValue("hooked", true)) { this.data = {} this.base64_auth = "" this.setState("hooked", false) return true } return false } } module.exports = { LCIConnector, LCIClient }