[Client] Confirm before joining a game opened from a game link (#7137)

* [Client] Confirm before joining a game opened from a game link

Joining a game from a cockatrice://joingame link is a navigation
decision, so restate what will be joined and ask before acting:
the confirm names the game description when the link carries one
(falling back to the room name and numeric id for older links), and
reports the host:port so links that point at a different server are
obvious. The intent chain is only started after confirmation.


Took 3 minutes

* [Client] Extract the join-game confirm message into a helper

Took 3 minutes

# Commit time for manual adjustment:
# Took 6 seconds

* Proper fwd declare.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-17 09:37:51 +02:00 committed by GitHub
parent b2cdf44bbd
commit 776f917ffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 0 deletions

View file

@ -1,5 +1,7 @@
#include "url_parser.h"
#include "../widgets/tabs/tab_room.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "../window_main.h"
#include "contexts/context_join_game.h"
#include "intent_join_server_game.h"
@ -9,6 +11,7 @@
#include <QMessageBox>
#include <QUrl>
#include <QUrlQuery>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <memory>
IntentUrlParser::IntentUrlParser(QObject *parent, MainWindow *_mainWindow) : QObject(parent), mainWindow(_mainWindow)
@ -71,6 +74,15 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
return;
}
const QString gameDescription = query.queryItemValue("game", QUrl::FullyDecoded);
const QString message = generateJoinGameMessage(*ctx, gameDescription);
const QMessageBox::StandardButton answer = QMessageBox::question(
mainWindow, tr("Join game"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (answer != QMessageBox::Yes) {
return;
}
// The join game intent owns the context and the credential lookup; once the
// chain finishes (or fails) it deletes the whole tree.
ContextConnectToServer *serverContext = &ctx->roomContext.serverContext;
@ -87,3 +99,38 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
getLoginCredentialsIntent->execute();
}
QString IntentUrlParser::generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription)
{
const QString hostname = context.roomContext.serverContext.hostname;
const QString port = context.roomContext.serverContext.port;
const int roomId = context.roomContext.roomId;
const int gameId = context.gameId;
const QString server = QStringLiteral("%1:%2").arg(hostname, port);
// Prefer the room name over the raw numeric id: it means something to the
// user. The name is only known when we are already connected to the same
// server and sitting in that room — otherwise fall back to a plain prompt.
AbstractClient *client = mainWindow->getTabSupervisor()->getClient();
const bool sameServer = client != nullptr && client->getStatus() == StatusLoggedIn &&
hostname.compare(client->serverName(), Qt::CaseInsensitive) == 0 &&
QString::number(client->serverPort()) == port;
TabRoom *roomTab = sameServer ? mainWindow->getTabSupervisor()->getRoomTabs().value(roomId) : nullptr;
const QString gameIdStr = QString::number(gameId);
// Links built by newer clients embed the game description ("game" item);
// restate it in the confirm so it matches what the chat anchor showed.
// Unknown query items are ignored, so old links without it keep working.
// The multi-arg .arg() overloads replace in a single pass, so a description
// containing "%…" cannot corrupt later placeholders.
// FullyDecoded undoes every %XX escape and must match the chat anchor's
// decode mode, so a description containing "%" reads identically in both.
if (gameDescription.isEmpty()) {
return roomTab ? tr("Join game #%1 in \"%2\" on %3?").arg(gameIdStr, roomTab->getRoomName(), server)
: tr("Join game #%1 on %2?").arg(gameIdStr, server);
}
return roomTab ? tr("Join game \"%1\" (#%2) in \"%3\" on %4?")
.arg(gameDescription, gameIdStr, roomTab->getRoomName(), server)
: tr("Join game \"%1\" (#%2) on %3?").arg(gameDescription, gameIdStr, server);
}

View file

@ -4,6 +4,7 @@
#include <QUrlQuery>
class MainWindow;
struct ContextJoinGame;
class IntentUrlParser : public QObject
{
Q_OBJECT
@ -14,6 +15,8 @@ public:
void handleJoinGame(const QUrlQuery &query);
private:
QString generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription);
MainWindow *mainWindow;
};