Skip to content

Commit

Permalink
Ubisoft: Update Ubisoft redemption widget to use RareCore
Browse files Browse the repository at this point in the history
* Load and populate Ubisoft information when the page is shown instead of startup.
* List all Ubisoft games, and differentiate based on whether they been redeemed.
  • Loading branch information
loathingKernel committed Nov 28, 2023
1 parent 0507091 commit c0ac23b
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 104 deletions.
2 changes: 1 addition & 1 deletion rare/components/tabs/games/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def __init__(self, parent=None):
)
self.ubisoft_group = UbisoftGroup(self.eos_ubisoft)
self.eos_group = EOSGroup(self.eos_ubisoft)
self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft.addWidget(self.eos_group)
self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft"))

self.setCurrentIndex(self.import_index)
Expand Down
261 changes: 158 additions & 103 deletions rare/components/tabs/games/integrations/ubisoft_group.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import time
import webbrowser
from logging import getLogger
from typing import Optional

from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSize
from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QSizePolicy, QPushButton, QGroupBox, QVBoxLayout
from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSize, pyqtSlot, Qt
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import (
QFrame,
QLabel,
QHBoxLayout,
QSizePolicy,
QPushButton,
QGroupBox,
QVBoxLayout,
)
from legendary.models.game import Game

from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton
from rare.lgndr.core import LegendaryCore
from rare.shared import RareCore
from rare.shared.workers.worker import Worker
from rare.utils.metrics import timelogger
from rare.utils.misc import icon
from rare.widgets.elide_label import ElideLabel
from rare.widgets.loading_widget import LoadingWidget

logger = getLogger("Ubisoft")

Expand All @@ -17,15 +31,16 @@ class UbiGetInfoWorker(Worker):
class Signals(QObject):
worker_finished = pyqtSignal(set, set, str)

def __init__(self):
def __init__(self, core: LegendaryCore):
super(UbiGetInfoWorker, self).__init__()
self.signals = UbiGetInfoWorker.Signals()
self.setAutoDelete(True)
self.core = LegendaryCoreSingleton()
self.core = core

def run_real(self) -> None:
try:
external_auths = self.core.egs.get_external_auths()
with timelogger(logger, "Request external auths"):
external_auths = self.core.egs.get_external_auths()
for ext_auth in external_auths:
if ext_auth["type"] != "ubisoft":
continue
Expand All @@ -35,12 +50,17 @@ def run_real(self) -> None:
self.signals.worker_finished.emit(set(), set(), "")
return

uplay_keys = self.core.egs.store_get_uplay_codes()
with timelogger(logger, "Request uplay codes"):
uplay_keys = self.core.egs.store_get_uplay_codes()
key_list = uplay_keys["data"]["PartnerIntegration"]["accountUplayCodes"]
redeemed = {k["gameId"] for k in key_list if k["redeemedOnUplay"]}

entitlements = self.core.egs.get_user_entitlements()
if (entitlements := self.core.lgd.entitlements) is None:
with timelogger(logger, "Request entitlements"):
entitlements = self.core.egs.get_user_entitlements()
self.core.lgd.entitlements = entitlements
entitlements = {i["entitlementName"] for i in entitlements}

self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id)
except Exception as e:
logger.error(str(e))
Expand All @@ -51,11 +71,11 @@ class UbiConnectWorker(Worker):
class Signals(QObject):
linked = pyqtSignal(str)

def __init__(self, ubi_account_id, partner_link_id):
def __init__(self, core: LegendaryCore, ubi_account_id, partner_link_id):
super(UbiConnectWorker, self).__init__()
self.signals = UbiConnectWorker.Signals()
self.setAutoDelete(True)
self.core = LegendaryCoreSingleton()
self.core = core
self.ubi_account_id = ubi_account_id
self.partner_link_id = partner_link_id

Expand All @@ -65,9 +85,7 @@ def run_real(self) -> None:
self.signals.linked.emit("")
return
try:
self.core.egs.store_claim_uplay_code(
self.ubi_account_id, self.partner_link_id
)
self.core.egs.store_claim_uplay_code(self.ubi_account_id, self.partner_link_id)
self.core.egs.store_redeem_uplay_codes(self.ubi_account_id)
except Exception as e:
self.signals.linked.emit(str(e))
Expand All @@ -76,55 +94,58 @@ def run_real(self) -> None:
self.signals.linked.emit("")


class UbiLinkWidget(QWidget):
def __init__(self, game: Game, ubi_account_id):
super(UbiLinkWidget, self).__init__()
self.args = ArgumentsSingleton()
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
class UbiLinkWidget(QFrame):
def __init__(self, game: Game, ubi_account_id, activated: bool = False, parent=None):
super(UbiLinkWidget, self).__init__(parent=parent)
self.setFrameShape(QFrame.StyledPanel)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

self.args = RareCore.instance().args()
self.game = game
self.ubi_account_id = ubi_account_id

self.title_label = QLabel(game.app_title)
layout.addWidget(self.title_label, stretch=1)

self.ok_indicator = QLabel()
self.ok_indicator.setPixmap(icon("fa.info-circle", color="grey").pixmap(20, 20))
self.ok_indicator = QLabel(parent=self)
self.ok_indicator.setPixmap(icon("fa.circle-o", color="grey").pixmap(20, 20))
self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
layout.addWidget(self.ok_indicator)

self.link_button = QPushButton(
self.tr("Redeem to Ubisoft") + ": Test" if self.args.debug else ""
)
layout.addWidget(self.link_button)
self.title_label = ElideLabel(game.app_title, parent=self)

self.link_button = QPushButton(self.tr("Redeem in Ubisoft"), parent=self)
self.link_button.setMinimumWidth(150)
self.link_button.clicked.connect(self.activate)
self.setLayout(layout)

if activated:
self.link_button.setText(self.tr("Already activated"))
self.link_button.setDisabled(True)
self.ok_indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20)))

layout = QHBoxLayout(self)
layout.setContentsMargins(-1, 0, 0, 0)
layout.addWidget(self.ok_indicator)
layout.addWidget(self.title_label, stretch=1)
layout.addWidget(self.link_button)

def activate(self):
self.link_button.setDisabled(True)
# self.ok_indicator.setPixmap(icon("mdi.loading", color="grey").pixmap(20, 20))
self.ok_indicator.setPixmap(
icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20)
)
self.ok_indicator.setPixmap(icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20))

if self.args.debug:
worker = UbiConnectWorker(None, None)
worker = UbiConnectWorker(RareCore.instance().core(), None, None)
else:
worker = UbiConnectWorker(self.ubi_account_id, self.game.partner_link_id)
worker = UbiConnectWorker(
RareCore.instance().core(), self.ubi_account_id, self.game.partner_link_id
)
worker.signals.linked.connect(self.worker_finished)
QThreadPool.globalInstance().start(worker)

def worker_finished(self, error):
if not error:
self.ok_indicator.setPixmap(
icon("ei.ok-circle", color="green").pixmap(QSize(20, 20))
)
self.ok_indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20)))
self.link_button.setDisabled(True)
self.link_button.setText(self.tr("Already activated"))
else:
self.ok_indicator.setPixmap(
icon("fa.info-circle", color="red").pixmap(QSize(20, 20))
)
self.ok_indicator.setPixmap(icon("fa.times-circle-o", color="red").pixmap(QSize(20, 20)))
self.ok_indicator.setToolTip(error)
self.link_button.setText(self.tr("Try again"))
self.link_button.setDisabled(False)
Expand All @@ -134,86 +155,120 @@ class UbisoftGroup(QGroupBox):
def __init__(self, parent=None):
super(UbisoftGroup, self).__init__(parent=parent)
self.setTitle(self.tr("Link Ubisoft Games"))
self.setLayout(QVBoxLayout())
self.core = LegendaryCoreSingleton()
self.args = ArgumentsSingleton()
self.rcore = RareCore.instance()
self.core = RareCore.instance().core()
self.args = RareCore.instance().args()

self.thread_pool = QThreadPool.globalInstance()
worker = UbiGetInfoWorker()
worker.signals.worker_finished.connect(self.show_ubi_games)
self.thread_pool.start(worker)
self.worker: Optional[UbiGetInfoWorker] = None

self.info_label = QLabel(parent=self)
self.info_label.setText(self.tr("Getting information about your redeemable Ubisoft games."))
self.browser_button = QPushButton(self.tr("Link Ubisoft acccount"), parent=self)
self.browser_button.setMinimumWidth(140)
self.browser_button.clicked.connect(
lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft")
)
self.browser_button.setEnabled(False)

self.loading_widget = LoadingWidget(self)
self.loading_widget.stop()

header_layout = QHBoxLayout()
header_layout.addWidget(self.info_label, stretch=1)
header_layout.addWidget(self.browser_button)

layout = QVBoxLayout(self)
layout.addLayout(header_layout)
layout.addWidget(self.loading_widget)

def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)

if self.worker is not None:
return

for widget in self.findChildren(UbiLinkWidget, options=Qt.FindDirectChildrenOnly):
widget.deleteLater()
self.loading_widget.start()

self.worker = UbiGetInfoWorker(self.core)
self.worker.signals.worker_finished.connect(self.show_ubi_games)
self.thread_pool.start(self.worker)
super().showEvent(a0)

@pyqtSlot(set, set, str)
def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
self.worker = None
if not redeemed and ubi_account_id != "error":
logger.error(
"No linked ubisoft account found! Link your accounts via your browser and try again."
)
self.layout().addWidget(
QLabel(
self.tr(
"Your account is not linked with Ubisoft. Please link your account first"
)
)
)
open_browser_button = QPushButton(self.tr("Open link page"))
open_browser_button.clicked.connect(
lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft")
self.info_label.setText(
self.tr("Your account is not linked with Ubisoft. Please link your account and try again.")
)
self.layout().addWidget(open_browser_button)
return
self.browser_button.setEnabled(True)
elif ubi_account_id == "error":
self.layout().addWidget(QLabel(self.tr("An error occurred")))
return
self.info_label.setText(
self.tr("An error has occurred while requesting your account's Ubisoft information.")
)
self.browser_button.setEnabled(True)
else:
self.browser_button.setEnabled(False)

games = self.core.get_game_list(False)
uplay_games = []
activated = 0
for game in games:
for dlc_data in game.metadata.get("dlcItemList", []):
if dlc_data["entitlementName"] not in entitlements:
continue
uplay_games = []
activated = 0
for rgame in self.rcore.ubisoft_games:
game = rgame.game
for dlc_data in game.metadata.get("dlcItemList", []):
if dlc_data["entitlementName"] not in entitlements:
continue
try:
app_name = dlc_data["releaseInfo"][0]["appId"]
except (IndexError, KeyError):
app_name = "unknown"

try:
app_name = dlc_data["releaseInfo"][0]["appId"]
except (IndexError, KeyError):
app_name = "unknown"
dlc_game = Game(app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data)
if dlc_game.partner_link_type != "ubisoft":
continue
if dlc_game.partner_link_id in redeemed:
continue
uplay_games.append(dlc_game)

dlc_game = Game(
app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data
)
if dlc_game.partner_link_type != "ubisoft":
if game.partner_link_type != "ubisoft":
continue
if dlc_game.partner_link_id in redeemed:
continue
uplay_games.append(dlc_game)

if game.partner_link_type != "ubisoft":
continue
if game.partner_link_id in redeemed:
activated += 1
continue
uplay_games.append(game)

if not uplay_games:
if activated >= 1:
self.layout().addWidget(
QLabel(
self.tr("All your Ubisoft games have already been activated")
)
)
if game.partner_link_id in redeemed:
activated += 1
uplay_games.append(game)

if not uplay_games:
self.info_label.setText(self.tr("You don't own any Ubisoft games."))
else:
self.layout().addWidget(
QLabel(self.tr("You don't own any Ubisoft games"))
if activated == len(uplay_games):
self.info_label.setText(self.tr("All your Ubisoft games have already been activated."))
else:
self.info_label.setText(
self.tr("You have <b>{}</b> games available to redeem.").format(
len(uplay_games) - activated
)
)
logger.info(f"Found {len(uplay_games) - activated} game(s) to redeem.")

self.loading_widget.stop()

for game in uplay_games:
widget = UbiLinkWidget(
game, ubi_account_id, activated=game.partner_link_id in redeemed, parent=self
)
self.layout().addWidget(widget)

if self.args.debug:
widget = UbiLinkWidget(
Game(app_name="Test", app_title="This is a test game"),
Game(app_name="RareTestGame", app_title="Super Fake Super Rare Super Test (Super?) Game"),
ubi_account_id,
activated=False,
parent=self,
)
self.layout().addWidget(widget)
return
logger.info(f"Found {len(uplay_games)} game(s) to redeem")

for game in uplay_games:
widget = UbiLinkWidget(game, ubi_account_id)
self.layout().addWidget(widget)
self.browser_button.setEnabled(True)
4 changes: 4 additions & 0 deletions rare/shared/rare_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,10 @@ def installed_games(self) -> Iterator[RareGame]:
def origin_games(self) -> Iterator[RareGame]:
return self.__filter_games(lambda game: game.is_origin and not game.is_dlc)

@property
def ubisoft_games(self) -> Iterator[RareGame]:
return self.__filter_games(lambda game: game.is_ubisoft and not game.is_dlc)

@property
def game_list(self) -> Iterator[Game]:
for game in self.games:
Expand Down
10 changes: 10 additions & 0 deletions rare/utils/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import time
from contextlib import contextmanager
from logging import Logger


@contextmanager
def timelogger(logger: Logger, title: str):
start = time.perf_counter()
yield
logger.debug("%s: %s seconds", title, time.perf_counter() - start)
Loading

0 comments on commit c0ac23b

Please sign in to comment.