Skip to content

QoL: premade group filter panel for the Group Finder - #1138

Open
nulltyto wants to merge 1 commit into
EllesmereGaming:mainfrom
nulltyto:feat/premade-group-filter
Open

QoL: premade group filter panel for the Group Finder#1138
nulltyto wants to merge 1 commit into
EllesmereGaming:mainfrom
nulltyto:feat/premade-group-filter

Conversation

@nulltyto

@nulltyto nulltyto commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Scope note: this is a first pass at the core filtering engine — getting the
right groups on screen without tainting the Group Finder. Visual customization
of the panel and the result rows is to follow in a later PR.

What does this PR do?

Adds a Filters panel beside Blizzard's Premade Groups browser that narrows a
search to the groups you actually want, plus two result-row display tweaks and a
new Group Finder page in the Quality of Life options. Every setting defaults
off, and nothing is hooked, framed or registered until the feature is first
enabled.

Filters panel (attached to LFGListFrame.SearchPanel, because these change
per search rather than per session):

  • Minimum leader score, needs a tank, needs a healer, my role available, fits my
    whole party.
  • Difficulty: Normal / Heroic / Mythic / Mythic+ for dungeons, Normal / Heroic /
    Mythic for raids.
  • Dungeon whitelist as a clickable icon grid (challenge-mode art with the house
    short names used elsewhere in the suite), and a raid whitelist. Both start
    collapsed showing only All / None; open them for per-instance picking. A
    whitelist filters by what it has in common with the list the current tab
    enumerates, so selections on Raids and Legacy Raids do not leak into each
    other and stale season entries simply go inert.
  • Optional user-written filter expression for people who want it
    (score > 2800 and tanks == 0).

Row display (two independent settings, usable without the filter panel):

  • Leader Mythic+ score per result row, rarity-coloured, on its own line above the
    role icons so nothing is clipped.
  • Role icons drawn as spec icons, or spec icons with a class-coloured bar.
    Blizzard's leaver-penalty badge is mirrored onto the overlay so it stays
    visible.

Settings consolidation: the four Group Finder settings that lived on the QoL
page (Auto Insert Keystone, Announce Instance Reset, Quick Signup, Persistent
Signup Note) move to the new page unchanged — same DB keys, same behaviour — so
everything Group Finder related is in one place.

Taint posture

This is the part I would most like scrutinised, and it is the reason the
implementation looks the way it does.

Groups are only ever removed through Blizzard's own advanced filter. The
panel builds a filter table and hands it to C_LFGList.SaveAdvancedFilter; the
search applies it server-side. That is a C call — nothing we pass becomes Lua
state Blizzard reads back, and the results arrive through Blizzard's own
untainted event path. This covers minimum leader score, the role needs, the four
dungeon difficulties and the dungeon whitelist.

An earlier revision of this PR filtered by rewriting panel.results, and that
was wrong.
Blizzard reads that array back into the elements it builds, so every
button.resultID is tainted; clicking a row carries the taint into
LFGListSearchPanel_SelectResult and then LFGListApplicationDialog_Show, which
writes LFGListApplicationDialog.activityID — a field Blizzard reads before it
writes
, so it can never be written securely again for the session. From that
point Blizzard's own result-list rebuild sorts tainted, and its comparator
compares numBNetFriends, which is a secret value inside an instance. One
sign-up from a filtered list was enough to produce pure-Blizzard error stacks
with this addon named. Three fields behave this way
(LFGListFrame.activePanel, ApplicationDialog.activityID,
SearchPanel.previousSearchText); none of them can be repaired short of
/reload. Probing, pausing and re-arming were all tried and all are too late.
The ownership seam is gone, and with it that entire class of bug.

What Blizzard's filter cannot express is dimmed rather than removed. It is
dungeons-only (LFGListSearchPanel_DoSearch nils the advanced filter for every
other category) and has no field for delisted, declined, party fit, the
expression box or any raid criterion. Those are applied as a read-only
verdict
that shades the row. Reading results taints nothing; only writing does.

Filter changes re-run the search immediately. A saved advanced filter
normally only takes effect on the next search, and we cannot start one —
C_LFGList.Search is flagged HasRestrictions, and
LFGListSearchPanel_DoSearch writes Blizzard state on our execution. So the
panel's click controls are SecureActionButtonTemplate buttons forwarding to
SearchPanel.RefreshButton: Blizzard's SECURE_ACTIONS.click runs the search in
its own context, not ours. The consequence is that these are protected frames, so
the panel's layout and attribute wiring defer to PLAYER_REGEN_ENABLED when they
are hit in combat. The score box is typed rather than clicked, so it carries its
own Apply button.

Forwarding is gated to the dungeon browse, because only a dungeon search can
act on what these controls save (see below). On the other tabs the clickbutton
attribute is cleared, which SECURE_ACTIONS.click handles by doing nothing — it
tests delegate and not delegate:IsForbidden() before clicking. PreClick still
fires, so the control's own work and the dimming pass still run; only the
pointless search is suppressed.

There are no sort controls. Reordering the browse means writing the results
array or its elements, which is the same poison by another route. Blizzard's own
comparator already sorts declined groups last and friends first.

Known limitation: raid criteria can only dim, not remove

Raid and Legacy Raid criteria (the raid whitelist and raid difficulty) shade
non-matching rows rather than removing them, and as far as I can establish there
is currently no way to do better. Every server-side lever is closed:

  • The advanced filter is dungeons-only. LFGListSearchPanel_DoSearch sets
    advancedFilters = nil for any category other than
    GROUP_FINDER_CATEGORY_ID_DUNGEONS, so re-running the search on a raid tab
    applies no filter at all. Blizzard's own UI agrees: FilterButton:SetupMenu
    only builds the advanced filter menu for dungeons, and the non-dungeon branch
    offers language options alone.
  • The search text is not a filter we can drive. C_LFGList.Search is called
    with nil in the text slot — the box is only used for the auto-create
    comparison. And the editbox is declared securityDisableSetText="true" with a
    secureReferenceKey, so it cannot be written to regardless.
  • panel.filters / panel.preferredFilters are off limits. They are
    Blizzard frame fields read back on every search; writing them is the ownership
    seam described above. They are also category-coarse — they are what the Raids
    vs Legacy Raids tabs themselves set.

So this is a Blizzard-side limitation rather than a design choice, and the panel
is explicit about it: the raid whitelist tooltip says non-matching raid groups
are shaded rather than removed. If a raid-capable filter API appears, the raid
criteria can move across to it without touching anything else — they are already
expressed the same way as the dungeon ones.

Remaining rules, all held to throughout:

  • We never call LFGListSearchPanel_DoSearch, C_LFGList.Search,
    LFGListSearchPanel_UpdateResults, LFGListSearchPanel_UpdateResultList or
    LFGListSearchEntry_Update.
  • No key is written onto any Blizzard frame or table. Module state lives in
    file-locals; anything keyed by a Blizzard row lives in an external weak-keyed
    table.
  • Every per-result read goes through a pcall'd accessor and every consumed
    field is issecretvalue()-checked. A failed or secret read makes the criterion
    pass (fail-open), and if anything comes back secret the verdict pass stands
    down entirely — reading a secret is safe, but comparing one throws.
  • HookScript, never SetScript, on Blizzard frames.
  • One deliberate exception, documented at its site: SetPoint is replaced on
    RaiderIO's own anchor frame so its profile window stops landing on top of the
    panel. That frame is third-party and non-secure; the same move on a Blizzard
    frame would be a taint bug.
  • The user-supplied expression runs inside a setfenv'd table holding nothing
    but sanitised scalars and four math helpers — no _G, no C_LFGList, no way
    back out. Every failure mode keeps the result.

While the panel is enabled it is the authority for the nine advanced-filter
fields it owns; the other seven are merged through untouched. The user's own
pre-takeover filter is snapshotted the first time the feature is enabled and
restored when it is switched off, so this is not a one-way door into a rewritten
dropdown.

Testing

  • Verified in content on 12.0.7 with group data protected —
    C_ChatInfo.InChatMessagingLockdown() true and 27 of 29
    GetSearchResultInfo fields secret, which is the state that used to detonate.
    An issecurevariable() sweep over the LFG frame fields, the LFGList globals
    and the data provider's resultIDs came back clean on every one. Taint is
    sticky until /reload, so a clean sweep after a session of filtering and
    signing up is a real result rather than a lucky sample.
  • Filtering, whitelists, difficulty, expression box and both row-display modes
    exercised on dungeon, raid and legacy-raid tabs.
  • Feature disabled: no frames, hooks or event registrations created.

@nulltyto
nulltyto marked this pull request as ready for review August 2, 2026 01:49
@nulltyto
nulltyto marked this pull request as draft August 2, 2026 01:58
@nulltyto
nulltyto force-pushed the feat/premade-group-filter branch from 15e5b72 to e9299ca Compare August 2, 2026 03:26
@nulltyto
nulltyto marked this pull request as ready for review August 2, 2026 03:31
@nulltyto
nulltyto marked this pull request as draft August 2, 2026 03:35
@nulltyto
nulltyto force-pushed the feat/premade-group-filter branch from e9299ca to 45853a9 Compare August 2, 2026 22:30
Adds a Filters panel beside the Premade Groups browser that narrows a
Dungeon search to the groups you actually want, plus result-row display
options and the Group Finder conveniences gathered onto one options page.

Filters panel (attached to the Group Finder, since these change per
search): minimum leader score, needs a tank, needs a healer, my role
available, fits my whole party, difficulty, a dungeon whitelist of icon
cells with All / None, a raid whitelist, and an optional user-written
filter expression (score > 2800 and tanks == 0).

Options page (Quality of Life -> Group Finder): master enable, dim
delisted / declined groups, the expression toggle, leader score on
entries, group member display (spec icons / spec icons + class bar),
quick signup, persistent signup note, auto insert keystone and instance
reset announce. The four that already existed move here unchanged, same
DB keys and behaviour.

Groups are removed from the list only through Blizzard's own advanced
filter (C_LFGList.SaveAdvancedFilter, applied server-side by the search),
never by rewriting the panel's results array -- that array is read back
into Blizzard's own sign-up and rebuild paths, and writing it permanently
taints the Group Finder for the session. That covers leader score, the
role needs, the four dungeon difficulties and the dungeon whitelist.

Blizzard's filter is dungeons-only and has no field for the rest, so
delisted, declined, party fit, the expression and all raid criteria are
applied as a read-only verdict that dims the row instead. Reading results
taints nothing. The verdict pass stands down entirely while the group
data is protected, since a secret value throws on comparison.

Changing any filter re-runs the search immediately: the controls are
SecureActionButtonTemplate buttons forwarding to the search panel's own
refresh button, so the search runs in Blizzard's execution rather than
ours. That makes them protected frames, so the panel's layout and wiring
defer to PLAYER_REGEN_ENABLED when they are hit in combat. The score box
is typed rather than clicked and carries its own Apply button.

Forwarding is gated to the dungeon browse. LFGListSearchPanel_DoSearch
discards the advanced filter for every other category, so a forwarded
search on Raids or Legacy Raids could not act on the change that
triggered it and would only spend a search against the server throttle.
On those tabs the clickbutton attribute is cleared; the control's own
work and the dimming pass still run, only the search is suppressed.

There are no sort controls: reordering means writing the results array,
with the same consequence. Blizzard's comparator already sorts declined
groups last and friends first.

Nothing is hooked, framed or registered until the feature is enabled.
Verified in content on 12.0.7 with group data protected.
@nulltyto
nulltyto force-pushed the feat/premade-group-filter branch from 45853a9 to 1360b46 Compare August 2, 2026 22:40
@nulltyto

nulltyto commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Note as of 2026-08-02 15:44 the dungeon filter functionality has been relatively sorted. Currently, the raid sortability as provided by blizzard is sub-par; which is limited our ability to filter without taint.

Will continue looking into approaches for the raid filtering.

@nulltyto
nulltyto marked this pull request as ready for review August 3, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant