Skip to content

Commit 299bc98

Browse files
version 0.4
1 parent ecc8778 commit 299bc98

File tree

5 files changed

+118
-92
lines changed

5 files changed

+118
-92
lines changed

requirements/app.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
galaxy.plugin.api==0.53
1+
galaxy.plugin.api==0.55
22
python-dateutil==2.8.0
33
requests==2.21.0
44
psutil==5.6.1

src/backend.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
11
import logging as log
2-
import asyncio
32

43

54
class ParadoxClient:
65
def __init__(self, http_client):
76
self.http_client = http_client
8-
self.paradox_launcher_skus = None
9-
10-
async def prepare_sku(self):
11-
data = {'token': self.http_client.token}
12-
log.info('Starting skus retrieve')
13-
response = await self.http_client.do_request('GET', 'https://accounts.paradoxplaza.com/api/skus', headers=data)
14-
response = await response.json()
15-
log.info('Finished skus retrieve')
16-
paradox_launcher_skus = set()
17-
for sku in response:
18-
await asyncio.sleep(0.01)
19-
if 'paradoxLauncher' in response[sku]['platform']:
20-
paradox_launcher_skus.add(sku)
21-
self.paradox_launcher_skus = paradox_launcher_skus
227

238
async def get_account_id(self):
249
data = {'Authorization': f'{{"session":{{"token":"{self.http_client.token}"}}}}',
@@ -34,13 +19,14 @@ async def get_owned_games(self):
3419
headers=data)
3520

3621
response = await response.json()
22+
3723
owned_products = []
3824
if 'products' in response:
3925
for platforms in response['products']:
4026
for platform in platforms:
4127
for game in platforms[platform]:
4228
log.info(game)
43-
if game['sku'] and game['title'] and game['product_type']:
29+
if game['sku'] and game['title'] and game['product_type'] and "paradox builds" in game['platforms']:
4430
owned_products.append({'sku': game['sku'],
4531
'title': game['title'],
4632
'type': game['product_type']})

src/consts.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
21
import re
2+
import sys
3+
from enum import EnumMeta
34

45

56
AUTH_URL = r"https://accounts.paradoxplaza.com/login"
@@ -9,9 +10,22 @@
910
PARADOX_LAUNCHER_EXE = "Paradox Launcher.exe"
1011

1112

13+
class System(EnumMeta):
14+
WINDOWS = 1
15+
MACOS = 2
16+
LINUX = 3
17+
18+
19+
if sys.platform == 'win32':
20+
SYSTEM = System.WINDOWS
21+
elif sys.platform == 'darwin':
22+
SYSTEM = System.MACOS
23+
24+
1225
def regex_pattern(regex):
1326
return ".*" + re.escape(regex) + ".*"
1427

28+
1529
AUTH_PARAMS = {
1630
"window_title": "Login to Paradox\u2122",
1731
"window_width": 700,

src/plugin.py

Lines changed: 99 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import logging as log
32
import sys
43

@@ -7,9 +6,9 @@
76
from galaxy.api.types import NextStep, Authentication, Game, LicenseInfo, LicenseType, LocalGame, LocalGameState
87
from version import __version__
98

10-
from consts import AUTH_PARAMS
119
from backend import ParadoxClient
1210
from http_client import AuthenticatedHttpClient
11+
from consts import AUTH_PARAMS, System, SYSTEM
1312

1413
import pickle
1514
import asyncio
@@ -19,8 +18,6 @@
1918
import webbrowser
2019

2120
from typing import Any, List, Optional
22-
23-
2421
from local import LocalClient
2522

2623

@@ -30,7 +27,6 @@ def __init__(self, reader, writer, token):
3027
self._http_client = AuthenticatedHttpClient(self.store_credentials)
3128
self.paradox_client = ParadoxClient(self._http_client)
3229
self.local_client = LocalClient()
33-
self.prepare_sku = None
3430
self.owned_games_cache = None
3531

3632
self.local_games_cache = {}
@@ -39,17 +35,20 @@ def __init__(self, reader, writer, token):
3935
self.tick_counter = 0
4036

4137
self.local_games_called = None
38+
self.owned_games_called = None
4239

4340
self.update_installed_games_task = None
4441
self.update_running_games_task = None
42+
self.update_owned_games_task = None
43+
44+
4545

4646
async def authenticate(self, stored_credentials=None):
4747
if stored_credentials:
4848
stored_cookies = pickle.loads(bytes.fromhex(stored_credentials['cookie_jar']))
4949
self._http_client.authenticate_with_cookies(stored_cookies)
5050
self._http_client.set_auth_lost_callback(self.lost_authentication)
5151
acc_id = await self.paradox_client.get_account_id()
52-
self.prepare_sku = asyncio.create_task(self.paradox_client.prepare_sku())
5352
return Authentication(str(acc_id), 'Paradox')
5453
if not stored_credentials:
5554
return NextStep("web_session", AUTH_PARAMS)
@@ -58,76 +57,85 @@ async def pass_login_credentials(self, step, credentials, cookies):
5857
self._http_client.authenticate_with_cookies(cookies)
5958
self._http_client.set_auth_lost_callback(self.lost_authentication)
6059
acc_id = await self.paradox_client.get_account_id()
61-
self.prepare_sku = asyncio.create_task(self.paradox_client.prepare_sku())
6260
return Authentication(str(acc_id), 'Paradox')
6361

6462
async def get_owned_games(self):
65-
owned_games = await self.paradox_client.get_owned_games()
66-
await self.prepare_sku
67-
games_to_send = []
68-
sent_titles = set()
69-
for game in owned_games:
70-
log.info(game)
71-
if game['sku'] in self.paradox_client.paradox_launcher_skus and 'game' in game['type']:
72-
title = game['title'].replace(' (Paradox)', '')
73-
title = title.split(':')[0]
74-
if title in sent_titles:
75-
continue
76-
sent_titles.add(title)
77-
games_to_send.append(Game(title.lower().replace(' ', '_'), title, None, LicenseInfo(LicenseType.SinglePurchase)))
78-
self.owned_games_cache = games_to_send
79-
return games_to_send
80-
81-
async def get_local_games(self):
82-
games_path = self.local_client.games_path
83-
if not games_path:
84-
return []
85-
local_games = os.listdir(games_path)
86-
8763
games_to_send = []
88-
local_games_cache = {}
89-
for local_game in local_games:
90-
game_folder = os.path.join(games_path, local_game)
91-
game_cpatch = os.path.join(game_folder, '.cpatch', local_game)
92-
try:
93-
with open(os.path.join(game_cpatch, 'version'))as game_cp:
94-
version = game_cp.readline()
95-
with open(os.path.join(game_cpatch, 'repository.json'), 'r') as js:
96-
game_repository = json.load(js)
97-
exe_path = game_repository['content']['versions'][version]['exePath']
98-
except FileNotFoundError:
99-
continue
100-
except Exception as e:
101-
log.error(f"Unable to parse local game {local_game} {repr(e)}")
102-
continue
103-
104-
local_games_cache[local_game] = os.path.join(game_folder, exe_path)
105-
games_to_send.append(LocalGame(local_game, LocalGameState.Installed))
106-
self.local_games_cache = local_games_cache
107-
self.local_games_called = True
64+
try:
65+
owned_games = await self.paradox_client.get_owned_games()
66+
sent_titles = set()
67+
for game in owned_games:
68+
log.info(game)
69+
if 'game' in game['type']:
70+
title = game['title'].replace(' (Paradox)', '')
71+
title = title.split(':')[0]
72+
if title in sent_titles:
73+
continue
74+
sent_titles.add(title)
75+
games_to_send.append(Game(title.lower().replace(' ', '_'), title, None, LicenseInfo(LicenseType.SinglePurchase)))
76+
self.owned_games_cache = games_to_send
77+
self.owned_games_called = True
78+
except Exception as e:
79+
log.error(f"Encountered exception while retriving owned games {repr(e)}")
80+
self.owned_games_called = True
81+
raise e
10882
return games_to_send
10983

110-
async def launch_game(self, game_id):
111-
exe_path = self.local_games_cache.get(game_id)
112-
log.info(f"Launching {exe_path}")
113-
game_dir = os.path.join(self.local_client.games_path, game_id)
114-
subprocess.Popen(exe_path,cwd=game_dir)
84+
if SYSTEM == System.WINDOWS:
85+
async def get_local_games(self):
86+
games_path = self.local_client.games_path
87+
if not games_path:
88+
self.local_games_called = True
89+
return []
90+
local_games = os.listdir(games_path)
91+
92+
games_to_send = []
93+
local_games_cache = {}
94+
for local_game in local_games:
95+
game_folder = os.path.join(games_path, local_game)
96+
game_cpatch = os.path.join(game_folder, '.cpatch', local_game)
97+
try:
98+
with open(os.path.join(game_cpatch, 'version'))as game_cp:
99+
version = game_cp.readline()
100+
with open(os.path.join(game_cpatch, 'repository.json'), 'r') as js:
101+
game_repository = json.load(js)
102+
exe_path = game_repository['content']['versions'][version]['exePath']
103+
except FileNotFoundError:
104+
continue
105+
except Exception as e:
106+
log.error(f"Unable to parse local game {local_game} {repr(e)}")
107+
continue
115108

116-
async def install_game(self, game_id):
117-
bootstraper_exe = self.local_client.bootstraper_exe
118-
if bootstraper_exe:
119-
subprocess.Popen(bootstraper_exe)
120-
return
121-
log.info("Local client not installed")
122-
webbrowser.open('https://play.paradoxplaza.com')
123-
124-
async def uninstall_game(self, game_id):
125-
bootstraper_exe = self.local_client.bootstraper_exe
126-
if bootstraper_exe:
127-
subprocess.call(bootstraper_exe)
128-
return
129-
log.info("Local client not installed")
130-
webbrowser.open('https://play.paradoxplaza.com')
109+
local_games_cache[local_game] = os.path.join(game_folder, exe_path)
110+
games_to_send.append(LocalGame(local_game, LocalGameState.Installed))
111+
self.local_games_cache = local_games_cache
112+
self.local_games_called = True
113+
return games_to_send
114+
115+
if SYSTEM == System.WINDOWS:
116+
async def launch_game(self, game_id):
117+
exe_path = self.local_games_cache.get(game_id)
118+
log.info(f"Launching {exe_path}")
119+
game_dir = os.path.join(self.local_client.games_path, game_id)
120+
subprocess.Popen(exe_path,cwd=game_dir)
121+
122+
if SYSTEM == System.WINDOWS:
123+
async def install_game(self, game_id):
124+
bootstraper_exe = self.local_client.bootstraper_exe
125+
if bootstraper_exe:
126+
subprocess.Popen(bootstraper_exe)
127+
return
128+
log.info("Local client not installed")
129+
webbrowser.open('https://play.paradoxplaza.com')
130+
131+
if SYSTEM == System.WINDOWS:
132+
async def uninstall_game(self, game_id):
133+
bootstraper_exe = self.local_client.bootstraper_exe
134+
if bootstraper_exe:
135+
subprocess.call(bootstraper_exe)
136+
return
137+
log.info("Local client not installed")
138+
webbrowser.open('https://play.paradoxplaza.com')
131139

132140
async def update_installed_games(self):
133141
games_path = self.local_client.games_path
@@ -165,18 +173,36 @@ async def update_running_games(self):
165173

166174
self.running_game = running_game
167175

176+
async def update_owned_games(self):
177+
owned_games_cache = self.owned_games_cache
178+
owned_games = await self.get_owned_games()
179+
log.info("Looking for new games")
180+
for game in owned_games:
181+
if game not in owned_games_cache:
182+
log.info(f"Adding game {game}")
183+
self.add_game(game)
184+
185+
168186
def tick(self):
169-
if not self.local_games_called or sys.platform != 'win32':
170-
return
171187
self.tick_counter += 1
172188

189+
if not self.owned_games_called or (sys.platform == 'win32' and not self.local_games_called):
190+
return
191+
192+
if self.tick_counter % 60 == 0:
193+
if not self.update_owned_games_task or self.update_owned_games_task.done():
194+
self.update_owned_games_task = asyncio.create_task(self.update_owned_games())
195+
196+
if sys.platform != 'win32':
197+
return
198+
173199
if not self.update_installed_games_task or self.update_installed_games_task.done():
174200
self.update_installed_games_task = asyncio.create_task(self.update_installed_games())
175201
if not self.update_running_games_task or self.update_running_games_task.done():
176202
self.update_running_games_task = asyncio.create_task(self.update_running_games())
177203

178-
def shutdown(self):
179-
asyncio.create_task(self._http_client.close())
204+
async def shutdown(self):
205+
await self._http_client.close()
180206

181207
async def prepare_os_compatibility_context(self, game_ids: List[str]) -> Any:
182208
return None

src/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3"
1+
__version__ = "0.4"

0 commit comments

Comments
 (0)