2024-12-12 17:12:13 +00:00
|
|
|
import base64
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
from pprint import pprint
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
from typing import Callable
|
|
|
|
|
2024-12-12 14:15:12 +00:00
|
|
|
import hid
|
2024-12-12 17:12:13 +00:00
|
|
|
import requests
|
|
|
|
import urllib3
|
|
|
|
|
|
|
|
# Local HTTPS connection, we need to not verify the cert
|
|
|
|
urllib3.disable_warnings()
|
|
|
|
|
|
|
|
log = logging.getLogger('valconomy')
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class ValorantPlayerInfo:
|
|
|
|
is_idle: bool = False
|
|
|
|
|
|
|
|
is_party_owner: bool = False
|
|
|
|
max_party_size: int = 0
|
|
|
|
party_size: int = 0
|
|
|
|
party_state: str = None
|
|
|
|
|
|
|
|
queue_type: str = None
|
|
|
|
game_state: str = None
|
|
|
|
|
|
|
|
map: str = None
|
|
|
|
team: str = None
|
|
|
|
score: int = 0
|
|
|
|
enemy_score: int = 0
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class RiotPlayerInfo:
|
|
|
|
uuid: str
|
|
|
|
name: str
|
|
|
|
tag: str
|
|
|
|
state: str
|
|
|
|
|
|
|
|
valorant: ValorantPlayerInfo = None
|
|
|
|
|
|
|
|
class ValorantLocalClient:
|
|
|
|
lockfile_path = os.path.join(os.environ['LOCALAPPDATA'], r'Riot Games\Riot Client\Config\lockfile')
|
|
|
|
poll_interval = 0.5
|
|
|
|
|
|
|
|
def __init__(self, callback: Callable[[RiotPlayerInfo], None]):
|
|
|
|
self.callback = callback
|
|
|
|
|
|
|
|
self.port, self.password = None, None
|
|
|
|
self.running = True
|
|
|
|
self.players = {}
|
|
|
|
|
|
|
|
def _load_credentials(self):
|
|
|
|
if not os.path.exists(self.lockfile_path):
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
with open(self.lockfile_path) as f:
|
|
|
|
for line in f:
|
|
|
|
if line.startswith('Riot Client'):
|
|
|
|
client, pid, self.port, self.password, proto = line.rstrip().split(':')
|
|
|
|
assert proto == 'https'
|
|
|
|
return True
|
|
|
|
except FileNotFoundError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _do_poll(self):
|
|
|
|
try:
|
|
|
|
resp = requests.get(
|
|
|
|
f'https://127.0.0.1:{self.port}/chat/v4/presences',
|
|
|
|
auth=('riot', self.password), verify=False)
|
|
|
|
resp.raise_for_status()
|
|
|
|
data = resp.json()
|
|
|
|
except (requests.HTTPError, requests.ConnectionError) as ex:
|
|
|
|
# Most likely one of these means the Riot Client shut down
|
|
|
|
log.error(f'Failed to make request to Riot Client: {ex}')
|
|
|
|
return False
|
|
|
|
|
|
|
|
for p in data['presences']:
|
|
|
|
if p['product'] != 'valorant':
|
|
|
|
continue
|
|
|
|
|
|
|
|
v = json.loads(base64.b64decode(p.pop('private'))) # Valorant-specfic
|
|
|
|
# pprint(p)
|
|
|
|
# pprint(v)
|
|
|
|
|
|
|
|
val_info = ValorantPlayerInfo(
|
|
|
|
is_idle=v['isIdle'], is_party_owner=v['isPartyOwner'],
|
|
|
|
max_party_size=v['maxPartySize'], party_size=v['partySize'],
|
|
|
|
party_state=v['partyState'],
|
|
|
|
queue_type=v['queueId'] or None, game_state=v['sessionLoopState'],
|
|
|
|
map=v['matchMap'] or None,
|
|
|
|
score=v['partyOwnerMatchScoreAllyTeam'], enemy_score=v['partyOwnerMatchScoreEnemyTeam'])
|
|
|
|
match v['partyOwnerMatchCurrentTeam']:
|
|
|
|
case 'Red':
|
|
|
|
val_info.team = 'attackers'
|
|
|
|
case 'Blue':
|
|
|
|
val_info.team = 'defenders'
|
|
|
|
|
|
|
|
info = RiotPlayerInfo(p['puuid'], p['game_name'], p['game_tag'], p['state'], valorant=val_info)
|
|
|
|
|
|
|
|
last = self.players.get(info.uuid)
|
|
|
|
if info != last:
|
|
|
|
self.players[info.uuid] = info
|
|
|
|
self.callback(info)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while self.running:
|
|
|
|
if self.password is None:
|
|
|
|
if not self._load_credentials():
|
|
|
|
time.sleep(self.poll_interval)
|
|
|
|
continue
|
|
|
|
log.info('Detected Riot Client credentials, starting polling')
|
|
|
|
|
|
|
|
if not self._do_poll():
|
|
|
|
self.port, self.password = None, None
|
|
|
|
|
|
|
|
time.sleep(self.poll_interval)
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self.running = False
|