mudlet/src/TLuaInterpreterDiscord.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

555 lines
19 KiB
C++
Raw Permalink Normal View History

/***************************************************************************
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
* Copyright (C) 2013-2022 by Stephen Lyons - slysven@virginmedia.com *
* Copyright (C) 2014-2017 by Ahmed Charles - acharles@outlook.com *
* Copyright (C) 2016 by Eric Wallace - eewallace@gmail.com *
* Copyright (C) 2016 by Chris Leacy - cleacy1972@gmail.com *
* Copyright (C) 2016-2018 by Ian Adkins - ieadkins@gmail.com *
* Copyright (C) 2017 by Chris Reid - WackyWormer@hotmail.com *
* Copyright (C) 2022-2023 by Lecker Kebap - Leris@mudlet.org *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
// mapper-specific functions of TLuaInterpreter, split out separately
// for convenience and to keep TLuaInterpreter.cpp size reasonable
#include "TLuaInterpreter.h"
#include "EAction.h"
#include "Host.h"
#include "TAlias.h"
#include "TArea.h"
#include "TCommandLine.h"
#include "TConsole.h"
#include "TDebug.h"
#include "TEvent.h"
#include "TFlipButton.h"
#include "TForkedProcess.h"
#include "TLabel.h"
#include "TMap.h"
#include "TMapLabel.h"
#include "TMedia.h"
#include "TRoomDB.h"
#include "TTabBar.h"
#include "TTextEdit.h"
#include "TTimer.h"
#include "dlgComposer.h"
#include "dlgIRC.h"
#include "dlgMapper.h"
#include "dlgModuleManager.h"
#include "dlgTriggerEditor.h"
#include "mapInfoContributorManager.h"
#include "mudlet.h"
#if defined(INCLUDE_3DMAPPER)
Improve: add a new, experimental 3D mapper (#8087) <!-- Keep the title short & concise so anyone non-technical can understand it, the title appears in PTB changelogs --> #### Brief overview of PR changes/additions This adds an experimental, new 3D mapper that uses shaders, more modern openGL, and a far better code reorganization that makes it an easier foundation to build upon. The new 3D mapper is here side by side with the original and can be toggled on for experimentation. There's a lot of work to be done, so I'd rather merge it early instead of making a mega-PR. #### Motivation for adding to Mudlet So we have a new foundation to build upon and improve. #### Other info (issues closed, discussion etc) Old and new mapper can be toggled dynamically with: ```lua -- this can be a keybinding setConfig("experiment.3dmap.modernmapper", not getConfig("experiment.3dmap.modernmapper")) ``` Smooth movement is one experiment in the new mapper, and it can be enabled with: ```lua lua setConfig("experiment.rendering.smooth-camera", true) ``` As you notice an experiments system has been added so we can implement things at once and experiment to choose the one that works best. This system can be used in other places in Mudlet as well. <details><summary>Details</summary> <p> ## Experiments System ### Overview Allows enabling/disabling experimental features via `setConfig`/`getConfig` with validation against a predefined whitelist. ### Usage ```lua -- Enable experiment setConfig("experiment.rendering.more-transparent", true) -- Check if enabled local enabled = getConfig("experiment.rendering.more-transparent") -- returns true/false -- Get active experiment in group local active = getConfig("experiment.rendering.active") -- returns "more-transparent" -- List all valid experiments local experiments = getConfig("experiment.list") -- returns table of valid keys ``` ### Behavior - Grouped experiments: Mutually exclusive (enabling one disables others in same group) - Validation: Only predefined experiments allowed, invalid keys return errors - Persistence: Experiment states saved/loaded with profiles ### Adding New Experiments Edit Host::mValidExperiments in src/Host.cpp: ```cpp const QSet<QString> Host::mValidExperiments = { qsl("experiment.rendering.originalish"), qsl("experiment.rendering.more-transparent"), qsl("experiment.newfeature.option1"), // Add here }; ``` ### Current Experiments - experiment.rendering.originalish - experiment.rendering.more-transparent </p> </details> --------- Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-29 12:15:48 +02:00
#include "glwidget_integration.h"
#endif
#include <limits>
#include <math.h>
#include <QCollator>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QFileInfo>
#include <QMovie>
#include <QVector>
#ifdef QT_TEXTTOSPEECH_LIB
#include <QTextToSpeech>
#endif // QT_TEXTTOSPEECH_LIB
// No documentation available in wiki - internal function
std::pair<bool, QString> TLuaInterpreter::discordApiEnabled(lua_State* L, bool writeAccess)
{
mudlet* pMudlet = mudlet::self();
if (!pMudlet->mDiscord.libraryLoaded()) {
return {false, qsl("Discord API is not available")};
}
auto& host = getHostFromLua(L);
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
if (host.mDiscordMode == Host::DiscordDisabled) {
return {false, qsl("Discord is disabled in settings")};
}
if (writeAccess && !pMudlet->mDiscord.discordUserIdMatch(&host)) {
return {false, qsl("Discord API is read-only as you're logged in with a different account in Discord compared to the one you entered for this profile")};
}
return {true, QString()};
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#usingMudletsDiscordID
int TLuaInterpreter::usingMudletsDiscordID(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
lua_pushboolean(L, pMudlet->mDiscord.usingMudletsDiscordID(&host));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordDetail
int TLuaInterpreter::getDiscordDetail(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
// Pushed as data, never as a format string: presence text can come from the
// game server, and a '%' in it would otherwise be taken as a printf
// specifier. The same holds for the five other Discord text getters below.
lua_pushstring(L, pMudlet->mDiscord.getDetailText(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordLargeIcon
int TLuaInterpreter::getDiscordLargeIcon(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
lua_pushstring(L, pMudlet->mDiscord.getLargeImage(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordLargeIconText
int TLuaInterpreter::getDiscordLargeIconText(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
lua_pushstring(L, pMudlet->mDiscord.getLargeImageText(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordParty
int TLuaInterpreter::getDiscordParty(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
QPair<int, int> const partyValues = pMudlet->mDiscord.getParty(&host);
lua_pushnumber(L, partyValues.first);
lua_pushnumber(L, partyValues.second);
return 2;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordSmallIcon
int TLuaInterpreter::getDiscordSmallIcon(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
lua_pushstring(L, pMudlet->mDiscord.getSmallImage(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordSmallIconText
int TLuaInterpreter::getDiscordSmallIconText(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
lua_pushstring(L, pMudlet->mDiscord.getSmallImageText(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordState
int TLuaInterpreter::getDiscordState(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
fix: Discord getters no longer treat presence text as a format string (#9660) #### Brief overview of PR changes/additions - The six Discord getters (`getDiscordDetail`, `getDiscordState`, `getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the stored presence text to `lua_pushfstring()` as its *format* string, so every `%` in it was read as a printf specifier consuming an argument that was never passed: `"Level %d Mage"` came back as `"Level 3202416 Mage"`, `"%%"` was silently halved, and a text ending in `%` read past the end of the buffer and returned the bytes that followed it as part of the Lua string. - All six now push the text as data. Output is byte-identical to today's for any text without a `%`. - Five regression specs added to `Discord_spec.lua`, covering all six getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the `pending()` entry that recorded this defect is unpended. #### Motivation for adding to Mudlet Presence text arrives from the game server over GMCP (`Host::processDiscordGMCP`), so a game whose status line contains a stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak adjacent heap bytes into a Lua string - for any user whose script reads the presence back. There is no write primitive (Lua 5.1's `lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory disclosure, not code execution. Stacks on #9631, whose Discord IPC fixture is what makes these specs possible; it retargets to `development` once that merges. **Test case:** all five new specs fail against the unfixed binary (`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at 50%` returned with trailing heap garbage) and pass with it; Lua suite 1853 successes / 0 failures / 0 errors / 40 pending twice with the fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the known-environmental `TKeySequenceEditTest` Xvfb flake). Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
lua_pushstring(L, pMudlet->mDiscord.getStateText(&host).toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDiscordTimeStamps
int TLuaInterpreter::getDiscordTimeStamps(lua_State* L)
{
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
QPair<int64_t, int64_t> const timeStamps = mudlet::self()->mDiscord.getTimeStamps(&host);
lua_pushnumber(L, timeStamps.first);
lua_pushnumber(L, timeStamps.second);
return 2;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#resetDiscordData
int TLuaInterpreter::resetDiscordData(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
pMudlet->mDiscord.resetData(&host);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordApplicationID
int TLuaInterpreter::setDiscordApplicationID(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
if (!lua_gettop(L)) {
pMudlet->mDiscord.setApplicationID(&host, QString());
lua_pushboolean(L, true);
return 1;
}
const QString inputText = getVerifiedString(L, __func__, 1, "Discord application ID").trimmed();
// Treat it as a UTF-8 string because although it is likely to be an
// unsigned long long integer (0 to 18446744073709551615) we want to
// be able to handle any input so we can report bad input strings back.
if (inputText.isEmpty()) {
// Empty string input - to reset to default the same as the no
// argument case:
pMudlet->mDiscord.setApplicationID(&host, QString());
// This must always succeed
lua_pushboolean(L, true);
return 1;
}
bool isOk = false;
quint64 const numericEquivalent = inputText.toULongLong(&isOk);
if (numericEquivalent && isOk) {
const QString appID = QString::number(numericEquivalent);
if (pMudlet->mDiscord.setApplicationID(&host, appID)) {
lua_pushboolean(L, true);
return 1;
}
return warnArgumentValue(L, __func__, qsl("'%1' does not appear to be a valid Discord application ID").arg(inputText));
}
return warnArgumentValue(L, __func__, qsl("'%1' can not be converted to the expected numeric Discord application ID").arg(inputText));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordDetail
int TLuaInterpreter::setDiscordDetail(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
auto discordText = getVerifiedString(L, __func__, 1, "text");
if (discordText.size() == 1) {
return warnArgumentValue(L, __func__, "text of length 1 not allowed by Discord");
}
pMudlet->mDiscord.setDetailText(&host, discordText);
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetDetail);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordElapsedStartTime
int TLuaInterpreter::setDiscordElapsedStartTime(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
const auto timeStamp = getVerifiedInt(L, __func__, 1, "epoch time");
if (timeStamp < 0) {
return warnArgumentValue(L, __func__, "the timestamp must be zero to clear the 'elapsed:' time or an epoch time value from the recent past");
}
pMudlet->mDiscord.setStartTimeStamp(&host, static_cast<int64_t>(timeStamp));
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetTimeInfo);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordGame
int TLuaInterpreter::setDiscordGame(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
const QString gamename = getVerifiedString(L, __func__, 1, "game name");
pMudlet->mDiscord.setDetailText(&host, tr("Playing %1").arg(gamename));
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetDetail);
pMudlet->mDiscord.setLargeImage(&host, gamename.toLower());
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetLargeIcon);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordGameUrl
int TLuaInterpreter::setDiscordGameUrl(lua_State* L)
{
// The invite URL changes what the Discord button opens, and the name is
// what it displays on the button. It is not part of rich presence, so it
// does not have the API enabled check that those Discord functions need
// in order to respect privacy.
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
const bool isActiveHost = (pMudlet->mpCurrentActiveHost == &host);
const int args = lua_gettop(L);
if (!args) { // no args, blank the invite URL and game name
host.setDiscordInviteURL(QString());
host.setDiscordGameName(QString());
if (isActiveHost) {
pMudlet->updateDiscordNamedIcon();
}
lua_pushboolean(L, true);
return 1;
}
Fix: strand no heap objects across the remaining lua_error() raises (#9605) #### Brief overview of PR changes/additions - Makes #9602's by-example sweep exhaustive. A static pass over the **674** functions taking a `lua_State*` - the only frames a `lua_error()` longjmp can unwind - found **377 sites in 160 functions** holding a QString, QStringList, QByteArray, std::string or TMediaData across a raise. **334 are converted, clearing 133 functions.** The **43 sites in 27 functions** left are each verified non-owning: `static` storage, `qsl()`/QStringLiteral, default-constructed or `""` (Qt's shared empty buffer), `isEmpty()` being the raise condition itself, a local declared in the branch that does not raise, and one `QFileInfo::exists` vs `TLuaInterpreter::exists` name collision. Same scanner both sides, nothing newly flagged. - Mechanism is #9602's: the `checkStringArg()` family, extended with `checkNumberArg`, `checkStringOrIntegerArg`, `checkCommandOrFunctionArg`, `checkCommandsOrFunctionsTable` and `checkHintsTable`. `getVerifiedString`/`Int`/`Bool`/`Float`/`Double`/`StringOrInteger`, `parseCommandOrFunction` and the two table parsers are reimplemented or paired on top, so message text and the order errors are reported in cannot drift. - Raisers are not only Mudlet's own helpers - lauxlib's `luaL_check*` and `luaL_opt*` raise too, and `spawn()` was the worst case. `TForkedProcess`'s constructor raised three times while holding the program name and the argument list it was still filling, and because a longjmp out of a constructor skips the rest of it, the `QProcess` that `startProcess()` had just `new`'d leaked whole. Checking and failure reporting move to `startProcess()`; the constructor no longer takes a `lua_State` and cannot raise. `waitForEvent()` swaps `luaL_checkstack` for the non-raising `lua_checkstack`. - Two sub-classes a site scan structurally cannot see were caught separately. Temporaries passed *into* a raising call rather than named locals: `setLabelCallback()` and `movieFunc()` take a `const char*` now, as a QByteArray built from their QString name was alive inside every raising check. And loop-carried accumulation, where a container looks non-owning at its declaration and only fills up once the loop runs - `setMergeTables("Char", {})` and `TForkedProcess`'s argument loop both stranded lists that way. A second detector (container declared outside a loop, mutated inside it, raise in the same body) reproduces both on the parent commit and reports nothing here. #### Motivation for adding to Mudlet #9602 removed the LSan suppression hiding this class but only fixed what the suite happened to reach, so the first spec touching any of the rest would turn CI red. Three things are worth recording for anyone repeating the audit: `lua_error` only unwinds frames between the raise and the enclosing `lua_pcall`, so closing the call graph over every function name gives 1834 "raisers" and thousands of bogus hits - the `lua_State*` universe is the right scope; `__func__` inside a lambda expands to `"operator()"`, so wrapping a function body in one silently renames its error messages; and a constructor that raises strands whatever `new`'d it. **Test case:** `Spawn_spec.lua` drives every `spawn()` error path, which strand 4260 bytes in 20 allocations on the parent commit and nothing here; `expandAlias`, `findItems` and `setModulePriority` likewise, with byte-identical error messages either side. Busted 2x 1316 successes / 0 failures / 0 errors / 0 pending, ctest 60/60. Stacked on #9602 - that one goes in first. Assisted-by: Claude:claude-opus-5
2026-08-03 11:39:32 +02:00
// argument 1 is applied before argument 2 is checked, as it was before:
// setDiscordInviteURL() persists to the profile, and hoisting the second
// check above it would stop a bad game name from saving the URL
if (!checkStringArg(L, __func__, 1, "url")) {
return lua_error(L);
}
{
const QString inviteUrl = QString{lua_tostring(L, 1)}.trimmed();
host.setDiscordInviteURL(inviteUrl.isEmpty() ? QString() : inviteUrl);
}
if (args > 1) {
Fix: strand no heap objects across the remaining lua_error() raises (#9605) #### Brief overview of PR changes/additions - Makes #9602's by-example sweep exhaustive. A static pass over the **674** functions taking a `lua_State*` - the only frames a `lua_error()` longjmp can unwind - found **377 sites in 160 functions** holding a QString, QStringList, QByteArray, std::string or TMediaData across a raise. **334 are converted, clearing 133 functions.** The **43 sites in 27 functions** left are each verified non-owning: `static` storage, `qsl()`/QStringLiteral, default-constructed or `""` (Qt's shared empty buffer), `isEmpty()` being the raise condition itself, a local declared in the branch that does not raise, and one `QFileInfo::exists` vs `TLuaInterpreter::exists` name collision. Same scanner both sides, nothing newly flagged. - Mechanism is #9602's: the `checkStringArg()` family, extended with `checkNumberArg`, `checkStringOrIntegerArg`, `checkCommandOrFunctionArg`, `checkCommandsOrFunctionsTable` and `checkHintsTable`. `getVerifiedString`/`Int`/`Bool`/`Float`/`Double`/`StringOrInteger`, `parseCommandOrFunction` and the two table parsers are reimplemented or paired on top, so message text and the order errors are reported in cannot drift. - Raisers are not only Mudlet's own helpers - lauxlib's `luaL_check*` and `luaL_opt*` raise too, and `spawn()` was the worst case. `TForkedProcess`'s constructor raised three times while holding the program name and the argument list it was still filling, and because a longjmp out of a constructor skips the rest of it, the `QProcess` that `startProcess()` had just `new`'d leaked whole. Checking and failure reporting move to `startProcess()`; the constructor no longer takes a `lua_State` and cannot raise. `waitForEvent()` swaps `luaL_checkstack` for the non-raising `lua_checkstack`. - Two sub-classes a site scan structurally cannot see were caught separately. Temporaries passed *into* a raising call rather than named locals: `setLabelCallback()` and `movieFunc()` take a `const char*` now, as a QByteArray built from their QString name was alive inside every raising check. And loop-carried accumulation, where a container looks non-owning at its declaration and only fills up once the loop runs - `setMergeTables("Char", {})` and `TForkedProcess`'s argument loop both stranded lists that way. A second detector (container declared outside a loop, mutated inside it, raise in the same body) reproduces both on the parent commit and reports nothing here. #### Motivation for adding to Mudlet #9602 removed the LSan suppression hiding this class but only fixed what the suite happened to reach, so the first spec touching any of the rest would turn CI red. Three things are worth recording for anyone repeating the audit: `lua_error` only unwinds frames between the raise and the enclosing `lua_pcall`, so closing the call graph over every function name gives 1834 "raisers" and thousands of bogus hits - the `lua_State*` universe is the right scope; `__func__` inside a lambda expands to `"operator()"`, so wrapping a function body in one silently renames its error messages; and a constructor that raises strands whatever `new`'d it. **Test case:** `Spawn_spec.lua` drives every `spawn()` error path, which strand 4260 bytes in 20 allocations on the parent commit and nothing here; `expandAlias`, `findItems` and `setModulePriority` likewise, with byte-identical error messages either side. Busted 2x 1316 successes / 0 failures / 0 errors / 0 pending, ctest 60/60. Stacked on #9602 - that one goes in first. Assisted-by: Claude:claude-opus-5
2026-08-03 11:39:32 +02:00
if (!checkStringArg(L, __func__, 2, "game name")) {
return lua_error(L);
}
host.setDiscordGameName(QString{lua_tostring(L, 2)}.trimmed());
} else {
host.setDiscordGameName(QString());
}
if (isActiveHost) {
pMudlet->updateDiscordNamedIcon();
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordLargeIcon
int TLuaInterpreter::setDiscordLargeIcon(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
pMudlet->mDiscord.setLargeImage(&host, getVerifiedString(L, __func__, 1, "key").toLower());
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetLargeIcon);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordLargeIconText
int TLuaInterpreter::setDiscordLargeIconText(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
auto discordText = getVerifiedString(L, __func__, 1, "text");
if (discordText.size() == 1) {
return warnArgumentValue(L, __func__, "text of length 1 not allowed by Discord");
}
pMudlet->mDiscord.setLargeImageText(&host, discordText);
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetLargeIconText);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordParty
int TLuaInterpreter::setDiscordParty(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
const auto partySize = getVerifiedInt(L, __func__, 1, "current party size");
if (partySize < 0) {
return warnArgumentValue(L, __func__, "the current party size must be zero or more");
}
int partyMax = -1;
if (lua_gettop(L) > 1) {
partyMax = getVerifiedInt(L, __func__, 2, "party maximum size", true);
if (partyMax < 0) {
return warnArgumentValue(L, __func__, "the optional party maximum size must be zero (to remove the party details) or more (to set the maximum)");
}
pMudlet->mDiscord.setParty(&host, partySize, partyMax);
} else {
// Only got the partySize now
pMudlet->mDiscord.setParty(&host, partySize);
}
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetPartyInfo);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordRemainingEndTime
int TLuaInterpreter::setDiscordRemainingEndTime(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
const auto timeStamp = getVerifiedInt(L, __func__, 1, "epoch time");
if (timeStamp < 0) {
return warnArgumentValue(L, __func__, "the timestamp must be zero to clear the 'remaining:' time or an epoch time value in the recent future");
}
pMudlet->mDiscord.setEndTimeStamp(&host, static_cast<int64_t>(timeStamp));
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetTimeInfo);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordSmallIcon
int TLuaInterpreter::setDiscordSmallIcon(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
pMudlet->mDiscord.setSmallImage(&host, getVerifiedString(L, __func__, 1, "key").toLower());
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetSmallIcon);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordSmallIconText
int TLuaInterpreter::setDiscordSmallIconText(lua_State* L)
{
mudlet* pMudlet = mudlet::self();
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
auto discordText = getVerifiedString(L, __func__, 1, "text");
if (discordText.size() == 1) {
return warnArgumentValue(L, __func__, "text of length 1 not allowed by Discord");
}
pMudlet->mDiscord.setSmallImageText(&host, discordText);
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
pMudlet->mDiscord.clearServerOrigin(&host, Host::DiscordSetSmallIconText);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDiscordState
int TLuaInterpreter::setDiscordState(lua_State* L)
{
auto& host = getHostFromLua(L);
auto result = discordApiEnabled(L, true);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
auto discordText = getVerifiedString(L, __func__, 1, "text");
if (discordText.size() == 1) {
return warnArgumentValue(L, __func__, "text of length 1 not allowed by Discord");
}
mudlet::self()->mDiscord.setStateText(&host, discordText);
improve: Give players full control over Discord Rich Presence (#9116) ## Summary Players had no clear way to control what Discord shows about their Mudlet activity. The old checkbox in the connection pane only gated server GMCP data but didn't prevent Discord from showing "Playing Mudlet", and the privacy controls were confusing. This PR replaces all of that with three straightforward modes via radio buttons in Profile Preferences > Chat: - **Show full game details (if supported)** - full game integration with server-provided presence (default) - **Show Mudlet only** - only shows "playing Mudlet", game server is not told about Discord - **Disabled** - Discord shows nothing about Mudlet Players pick the mode that matches their comfort level, and the existing privacy checkboxes (hide detail, hide state, etc.) remain available in Game details mode for finer control. ### What changed - **Three-mode radio buttons** in Profile Preferences > Chat with a two-column layout (modes on the left, privacy controls on the right), replacing the old connection-pane checkbox - **Server-origin tracking** so privacy checkboxes only gate data sent by the game server - Lua API calls always pass through (only Disabled mode blocks Lua entirely) - **Mid-session mode switching** via dynamic GMCP negotiation (Core.Supports.Add/Remove + External.Discord.Hello/Get) - **Deferred RPC init** - Discord RPC now starts when a profile loads, not on app launch - **Username restriction improvements** - takes effect immediately, case-insensitive (Discord usernames are lowercase-only since 2023), shuts down RPC when mismatched - **Shows logged-in Discord user** in preferences next to the restriction field, with a tooltip explaining the desktop app requirement when not connected - **Presence fix** - empty string fields now send nullptr so Discord hides them instead of showing blanks - **Memory leak fix** - presence allocations are now freed in the destructor regardless of RPC state ### Cleanup - Removed obsolete discriminator field (`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators in 2023 - Removed dead code (`getDiscordUserDetails()`, never called) - Restored `Discord_ClearPresence` function pointer for potential future use - Use proper `Host::DiscordOptionFlags` types instead of raw `int` (thanks @SlySven) ### Known quirks - The "Hide timer" checkbox correctly omits timestamps from presence data, but Discord's client starts its own activity timer for any presence without a timestamp - this is Discord client behavior outside our control. - The "Hide large icon" setting clears the image key, but some Discord clients fall back to the application's default icon instead of hiding it entirely. ### Test plan - [ ] Open Profile Preferences > Chat tab - [ ] Switch between the three radio button modes and verify Discord presence updates accordingly - [ ] In Game details mode, toggle privacy checkboxes and verify fields are hidden/shown - [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode - should work. Try in Disabled mode - should fail with error - [ ] Set a username restriction and verify presence clears immediately if mismatched - [ ] Run unit tests: `cd build && ./test/DiscordTest` - [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V` Closes #6967. Supersedes #7438. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
mudlet::self()->mDiscord.clearServerOrigin(&host, Host::DiscordSetState);
lua_pushboolean(L, true);
return 1;
}