-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
40647f6
commit 99ac6c3
Showing
11 changed files
with
452 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[pytest] | ||
python_paths = src | ||
addopts = src tests --flakes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
galaxy.plugin.api==0.53 | ||
python-dateutil==2.8.0 | ||
requests==2.21.0 | ||
psutil==5.6.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
-r app.txt | ||
invoke==1.2.0 | ||
pytest==4.3.0 | ||
pytest-flakes==4.0.0 | ||
pytest-pythonpath==0.7.3 | ||
pytest-asyncio==0.10.0 | ||
pytest-mock==1.10.4 | ||
pip-tools==3.6.1 | ||
aiohttp==3.5.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import logging as log | ||
import asyncio | ||
|
||
|
||
class ParadoxClient: | ||
def __init__(self, http_client): | ||
self.http_client = http_client | ||
self.paradox_launcher_skus = None | ||
|
||
async def prepare_sku(self): | ||
data = {'token': self.http_client.token} | ||
log.info('Starting skus retrieve') | ||
response = await self.http_client.do_request('GET', 'https://accounts.paradoxplaza.com/api/skus', headers=data) | ||
response = await response.json() | ||
log.info('Finished skus retrieve') | ||
paradox_launcher_skus = set() | ||
for sku in response: | ||
await asyncio.sleep(0.01) | ||
if 'paradoxLauncher' in response[sku]['platform']: | ||
paradox_launcher_skus.add(sku) | ||
self.paradox_launcher_skus = paradox_launcher_skus | ||
|
||
async def get_account_id(self): | ||
data = {'Authorization': f'{{"session":{{"token":"{self.http_client.token}"}}}}', | ||
'content-type': 'application/json'} | ||
response = await self.http_client.do_request('GET', 'https://api.paradox-interactive.com/accounts', headers=data) | ||
response = await response.json() | ||
return response['id'] | ||
|
||
async def get_owned_games(self): | ||
data = {'Authorization': f'{{"session":{{"token":"{self.http_client.token}"}}}}', | ||
'content-type': 'application/json'} | ||
response = await self.http_client.do_request('GET', 'https://api.paradox-interactive.com/inventory/products', | ||
headers=data) | ||
|
||
response = await response.json() | ||
owned_products = [] | ||
if 'products' in response: | ||
for platforms in response['products']: | ||
for platform in platforms: | ||
for game in platforms[platform]: | ||
log.info(game) | ||
if game['sku'] and game['title'] and game['product_type']: | ||
owned_products.append({'sku': game['sku'], | ||
'title': game['title'], | ||
'type': game['product_type']}) | ||
log.info(owned_products) | ||
return owned_products |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
|
||
import re | ||
|
||
|
||
AUTH_URL = r"https://accounts.paradoxplaza.com/login" | ||
AUTH_REDIRECT_URL = r"api/accounts/connections/" | ||
|
||
REGISTRY_LAUNCHER_PATH = r"SOFTWARE\WOW6432Node\Paradox Interactive\Paradox Launcher\LauncherPath" | ||
PARADOX_LAUNCHER_EXE = "Paradox Launcher.exe" | ||
|
||
|
||
def regex_pattern(regex): | ||
return ".*" + re.escape(regex) + ".*" | ||
|
||
AUTH_PARAMS = { | ||
"window_title": "Login to Paradox\u2122", | ||
"window_width": 700, | ||
"window_height": 800, | ||
"start_uri": AUTH_URL, | ||
"end_uri_regex": regex_pattern(AUTH_REDIRECT_URL) | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
|
||
from galaxy.http import HttpClient | ||
|
||
import aiohttp | ||
import logging as log | ||
from yarl import URL | ||
import pickle | ||
|
||
class CookieJar(aiohttp.CookieJar): | ||
def __init__(self): | ||
super().__init__() | ||
self._cookies_updated_callback = None | ||
|
||
def set_cookies_updated_callback(self, callback): | ||
self._cookies_updated_callback = callback | ||
|
||
def update_cookies(self, cookies, url=URL()): | ||
super().update_cookies(cookies, url) | ||
if cookies and self._cookies_updated_callback: | ||
self._cookies_updated_callback(list(self)) | ||
|
||
|
||
class AuthenticatedHttpClient(HttpClient): | ||
|
||
def __init__(self, store_credentials): | ||
self._store_credentials = store_credentials | ||
self.token = None | ||
|
||
self.bearer = None | ||
self.user = None | ||
self._cookie_jar = CookieJar() | ||
self._auth_lost_callback = None | ||
|
||
super().__init__(cookie_jar=self._cookie_jar) | ||
|
||
def set_cookies_updated_callback(self, callback): | ||
self._cookie_jar.set_cookies_updated_callback(callback) | ||
|
||
def update_cookies(self, cookies): | ||
self._cookie_jar.update_cookies(cookies) | ||
|
||
def set_auth_lost_callback(self, callback): | ||
self._auth_lost_callback = callback | ||
|
||
|
||
def get_credentials(self): | ||
creds = {} | ||
creds['cookie_jar'] = pickle.dumps([c for c in self._cookie_jar]).hex() | ||
return creds | ||
|
||
async def do_request(self, method, *args, **kwargs): | ||
try: | ||
return await self.request(method, *args, **kwargs) | ||
except Exception as e: | ||
log.warning(f"Request failed with {repr(e)}, attempting to refresh credentials") | ||
#await self.refresh_credentials() | ||
return await self.request(method, *args, **kwargs) | ||
|
||
def authenticate_with_cookies(self, cookies): | ||
cookiez = {} | ||
for cookie in cookies: | ||
if 'value' in cookie: | ||
cookiez[cookie['name']] = cookie['value'] | ||
else: | ||
cookiez[cookie.key] = cookie.value | ||
self.update_cookies(cookiez) | ||
self.token = cookiez['SESSION_TOKEN'] | ||
self._store_credentials(self.get_credentials()) | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import sys | ||
if sys.platform == 'win32': | ||
import winreg | ||
|
||
import os | ||
import logging as log | ||
import asyncio | ||
from consts import REGISTRY_LAUNCHER_PATH, PARADOX_LAUNCHER_EXE | ||
from dataclasses import dataclass | ||
from galaxy.proc_tools import process_iter, ProcessInfo | ||
|
||
@dataclass | ||
class RunningGame: | ||
name: str | ||
process: ProcessInfo | ||
|
||
|
||
class LocalClient(object): | ||
def __init__(self): | ||
self._local_client_path = None | ||
self._local_client_exe = None | ||
self._local_games_path = None | ||
|
||
|
||
@property | ||
def installed(self): | ||
if self._local_client_exe and os.access(self._local_client_exe, os.F_OK): | ||
return True | ||
else: | ||
self.refresh_local_client_state() | ||
return self._local_client_exe and os.access(self._local_client_exe, os.F_OK) | ||
|
||
@property | ||
def local_client_exe(self): | ||
if self.installed: | ||
return self._local_client_exe | ||
|
||
@property | ||
def local_client_path(self): | ||
if self.installed: | ||
return self._local_client_path | ||
|
||
@property | ||
def bootstraper_exe(self): | ||
if self.installed: | ||
paradox_root = self._local_client_path[:-len('\\launcher')] | ||
bootstrapper = os.path.join(paradox_root, 'bootstrapper') | ||
return os.path.join(bootstrapper,'Bootstrapper.exe') | ||
|
||
@property | ||
def games_path(self): | ||
if self.installed: | ||
if self._local_games_path: | ||
return self._local_games_path | ||
else: | ||
paradox_root = self._local_client_path[:-len('\\launcher')] | ||
paradox_games = os.path.join(paradox_root, 'games') | ||
if not os.access(paradox_games, os.F_OK): | ||
return None | ||
self._local_games_path = paradox_games | ||
return paradox_games | ||
|
||
def refresh_local_client_state(self): | ||
if sys.platform == 'win32': | ||
try: | ||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,REGISTRY_LAUNCHER_PATH, 0, winreg.KEY_READ) as key: | ||
local_client_path = winreg.QueryValueEx(key, "Path")[0] | ||
local_client_exe = os.path.join(local_client_path, PARADOX_LAUNCHER_EXE) | ||
self._local_client_path = local_client_path | ||
self._local_client_exe = local_client_exe | ||
except OSError: | ||
self._local_client_exe = self._local_client_path = self._local_games_path = None | ||
|
||
async def get_running_game(self, games, proc_iter_interval=0.05): | ||
if not games: | ||
return | ||
for process_info in process_iter(): | ||
try: | ||
await asyncio.sleep(proc_iter_interval) | ||
for game in games: | ||
if process_info.binary_path.lower() == games[game].lower(): | ||
log.info(f"Found a running game! {game}") | ||
return RunningGame(name=game, process=process_info) | ||
except: | ||
continue |
Oops, something went wrong.