[UserList] Context menu invite (#7138)

* [Client] Send game invites from the user context menu via a private message

The user context menu gains an "Invite to Game" submenu listing the
inviteable games in the room (the inviter's own games, honoring the
buddy-only setting). Picking one opens a private message to the target
user with a cockatrice://joingame link naming the game, so the target
gets a clickable invite instead of a raw URL. Multi-game rooms offer a
picker; a single inviteable game sends directly. Sending a message to
an offline user no longer swallows the draft — it reports that the
user is offline and keeps the typed text.

Took 50 seconds


Took 3 minutes

* [Client] Extract sendPrivateMessage() to fix invite message draft overwrite

sendInviteMessage() was calling sayEdit->setText(text) then sendMessage(),
which overwrites any text the user had typed.  Extract the command-building
and sending logic into a new sendPrivateMessage(const QString &text) method
that takes the text directly.  sendMessage() now calls it after its guards
and clears sayEdit; sendInviteMessage() calls it directly without touching
the input field at all.

Took 33 minutes

* Rename method, address comments.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-17 10:49:19 +02:00 committed by GitHub
parent 776f917ffc
commit 765ebf8fb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 179 additions and 14 deletions

View file

@ -3,6 +3,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/server/user/user_list_widget.h"
#include "../main.h"
@ -950,6 +951,53 @@ void TabSupervisor::talkLeft(TabMessage *tab)
removeTab(indexOf(tab));
}
QList<GameInviteOption> TabSupervisor::getGameInviteLinksForRoom(int roomId) const
{
QList<GameInviteOption> options;
if (isLocalGame) {
return options;
}
// The inviter may be in several games of the same room (hosting one and
// spectating another, for example). Return every game so the caller can
// let the user choose which one to invite to.
for (TabGame *tab : gameTabs) {
GameMetaInfo *metaInfo = tab->getGame()->getGameMetaInfo();
if (metaInfo->proto().room_id() != roomId) {
continue;
}
// A closed game is a dead end — drop it. Started/full games stay
// listed: an invite to them is a legitimate "come spectate" offer.
if (metaInfo->proto().closed()) {
continue;
}
const int gameId = metaInfo->gameId();
const QString description = QString::fromStdString(metaInfo->proto().description());
GameInviteOption option{
.gameId = gameId,
.label =
description.isEmpty() ? tr("Game #%1").arg(gameId) : tr("Game #%1 — %2").arg(gameId).arg(description),
.url = makeGameJoinLink(client->serverName(), client->serverPort(), roomId, gameId, description),
.description = description,
.onlyBuddies = metaInfo->proto().only_buddies(),
.creatorName = QString::fromStdString(metaInfo->proto().creator_info().name()),
};
options.append(option);
}
return options;
}
void TabSupervisor::sendInviteToUser(const QString &userName, const QString &inviteText)
{
TabMessage *tab = addMessageTab(userName, true);
if (tab && tab->isUserOnline()) {
tab->sendPrivateMessage(inviteText);
}
}
/**
* Creates a new deck editor tab and loads the deck into it.
* Creates either a classic or visual deck editor tab depending on settings