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
JavaScript
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
}