Commit graph

4 commits

Author SHA1 Message Date
Vadim Peretokin
b741663a1c
infrastructure: drop comments that restate the code beside them (#9681)
#### Brief overview of PR changes/additions
- Removed 8 comments that only repeated the statement or assertion
message next to them
- Kept 1 of the 16 identical copies of the `lua_next()` key-copy note in
`TLuaInterpreterMedia.cpp`
- Comment-only: zero code lines changed

#### Motivation for adding to Mudlet
Reading a comment and then the code that says the same thing is wasted
effort; the rationale comments that document real gotchas are all
untouched.

#### Other info (issues closed, discussion etc)
Result of a pass over the last month of commits on `development`. The
vast majority of comments added there explain *why* rather than restate
*what*, so this is deliberately a small diff.

**Test case:** `git diff development...HEAD` shows only comment lines
removed; build and test suites are unaffected.


Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-08-11 08:06:16 +02:00
Vadim Peretokin
c0e1fac7cb
fix: profile close during a map operation, Discord presence truncation, and interrupting ttsSpeak() (#9686)
#### Brief overview of PR changes/additions
- Closing a profile no longer frees the map out from under a running
import, export or download. `TMap` counts the operations that pump
`qApp->processEvents()`, and `mudlet::closeHost()` - which one of those
pumps is what delivers it - stops the operation and destroys the `Host`
once it has unwound, instead of half way through it.
- Discord presence fields keep their last character and are only ever
cut between characters: each buffer is now the documented limit plus
room for its terminator, and a new `utils::copyUtf8String()` walks the
cut back to a character boundary.
- An interrupting `ttsSpeak()` announces the utterance it starts, and
the `Ready` an engine reports for the utterance it cut off no longer
drains `ttsQueue()` over the top of the one the script asked for.

#### Motivation for adding to Mudlet
Each is a filed defect, and each was reproduced before it was fixed. The
map one is a use-after-free: ASan reports `heap-use-after-free` inside
`TMap::readJsonMapFile()`, freed by `~TMap` <- `~Host` <-
`HostManager::deleteHost` <- `mudlet::closeHost` delivered by the
import's own `processEvents()`. The Discord one is worse than one field
looking wrong: a single over-long non-ASCII field makes the whole
`SET_ACTIVITY` payload undecodable, so the entire presence update is
discarded - the fake Discord client recorded exactly that. The TTS one
silently drops speech: `ttsQueue()` plus an interrupting `ttsSpeak()`
speaks the queued line and never speaks the requested one.

#### Other info (issues closed, discussion etc)
Closes #9520, closes #9634, closes #9659.

`MapCloseDuringImportTest` stages the close through
`mudlet::slot_closeProfileByName()` and lets the map operation's own
pump deliver it; the functional tests build with ASan, so the pre-fix
run is a sanitizer report rather than an inference.
`TtsInterruptingSpeakTest` hands `ttsStateChanged()` the `Ready` a real
engine sends, which Qt's mock engine never does - the mock-visible half
is pinned in `Media_spec.lua`, where the two specs that recorded the old
behaviour are updated. `Discord_spec.lua` gains four end-to-end specs
against `CI/discord-ipc-fixture.py` asserting that the captured frame
still decodes as JSON and that a field is cut on a character boundary,
and `DiscordTest.cpp` covers the same at unit level. Every new or
changed test was confirmed to fail without its fix.

Two things deliberately left alone, both older than this PR:
`Host::requestClose()` still runs nested inside the map operation's pump
(it saves the profile there), and an XML import or a map download has no
cancel to poll, so a close waits for it rather than stopping it.

**Test case:** Export a large map with `exportJsonMap()` and close the
profile's tab while it runs; then `setDiscordDetail(string.rep("ä",
65))` and confirm the presence still updates; then `ttsQueue("queued
line") ttsSpeak("first")` followed immediately by `ttsSpeak("second")`
and confirm "second" is what gets spoken.

Assisted-by: Claude:claude-opus-5
2026-08-07 06:10:42 +02:00
Vadim Peretokin
dc2e639132
Fix: apply Discord API permission gating consistently (#9472)
#### Brief overview of PR changes/additions

Since the original 2018 Discord commit, 14 of the 22 Lua Discord
functions carried inverted permission gating from copy-paste. Four
getters required WRITE access and failed with a self-contradictory "API
is read-only" error, nine setters only checked READ so scripts could
mutate presence in read-only mode, and `resetDiscordData` had no gate at
all. Getters now check read access; setters and `resetDiscordData` check
write access. The permission *meanings* are unchanged and the GMCP
server path is unaffected.

#### Motivation for adding to Mudlet

Gating is now consistent: read-only scripts can no longer mutate Discord
presence, and legitimate getters no longer fail with a contradictory
error.

#### Other info (issues closed, discussion etc)

Tests: a source-scan contract test in `DiscordTest` enforcing the full
22-function gating matrix (it fails against the pre-fix source), plus 5
Lua-level functional cases in `TDiscordModeTest` with the bundled
discord-rpc actually loaded.

Tested by hand. Squash-merge with:
```
Assisted-by: Claude:claude-fable-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
```


https://github.com/user-attachments/assets/99850189-f483-43e5-b46f-1823df8699dc
2026-07-26 10:13:28 +02:00
Morquin
3036e1f3d7
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