Skip to content

feat(plugin-gateway): remaining discord.js Util helpers #159

Open

Description

@RedStar071

Tracking the parts of discord.js's Util.js that @wolfstar/plugin-gateway still lacks.

Already ported: parseEmoji, resolvePartialEmoji, resolveColor (#157); flatten, cleanContent, cleanCodeBlockContent, parseWebhookURL, verifyString, discordSort, moveElementInArray, getSortableGroupTypes, makeError, makePlainError, basename, findName, resolveSKUId (#158); fetchRecommendedShardCount (in @wolfstar/plugin-sharder); sorted setPosition (via computePositions) and transformResolved (#160).

Still missing

  • resolveGuildEmoji(client, emojiId): looks up a custom emoji across every cached guild. With the async cache this needs an emoji index keyed by ID, or a scan of each guild's emoji cache.

Follow-ups to the position work (#160)

  • A computed, sorted position (discord.js GuildChannel#position / Role#position), with the API value exposed as rawPosition. Channel and role position currently return the raw API value, because a synchronous sorted getter would have to read every sibling from a possibly async cache. We could expose it as fetchPosition(), or resolve it in _hydrate.
  • Sort the cached channels and roles of a guild without refetching them. fetchSorted and RoleManager#setPosition call the API because the cache cannot list entities by guild.
  • GuildChannelManager#setPositions / RoleManager#setPositions accepting relative entries.

Follow-ups to transformResolved (#160)

  • messages (resolved through client.messages and the channel), and attachments (built as Attachment), like discord.js.
  • Decide whether MessageMentions should resolve the raw mentioned users and members from the payload (discord.js _adds them, so parsedUsers includes them even when they were not cached). It currently only reads them by ID from the cache.

Activity

  1. pullfrog commented on Sep 28, 2026

    @pullfrog
    Contributor

    Findings

    I checked issue #159's checklist against the current state of main (HEAD 53aee8f, which is #158). The issue was filed slightly out of date: most of the "Already ported" caveat items and several checklist boxes are now done. Concretely, packages/plugin-gateway/src/util/Util.ts already exports cleanContent (handles /command and :emoji:, not just @/#/role mentions), cleanCodeBlockContent, parseWebhookURL, verifyString, flatten, discordSort, getSortableGroupTypes, moveElementInArray, makeError/makePlainError, basename/findName, and resolveSKUId. GuildChannelManager#setPositions and RoleManager#setPosition(s) exist. fetchRecommendedShardCount already lives in plugin-sharder/src/util/gateway.ts, as the issue itself suggested. MessageMentions already resolves users/members/roles/channels via transformResolved-backed relations.

    What's still genuinely open, verified by reading the current source:

    1. resolveGuildEmoji — no matches anywhere in src/. Not started.
    2. MessagePayload's nameOf() (structures/messages/MessagePayload.ts:688-694) still imports basename from node:path and reimplements URL/query-string handling locally, instead of calling Util.ts's basename/findName. This is exactly the duplication the issue's private-helpers section flags.
    3. flatten and resolveSKUId are implemented but have no callers — flatten because structures already expose raw data (per the issue's own note), resolveSKUId because no SKU structure exists yet to consume it. These need an explicit keep/remove decision, not blind implementation.
    4. Position follow-ups, all still open:
      • Role#position (structures/guilds/Role.ts:106) and the channel equivalent (ChannelPermissionMixin.ts:47) return the raw API value, not a computed Discord-order position with rawPosition split out.
      • No fetchSorted-equivalent exists — the cache can't list a guild's channels/roles to sort without a REST refetch.
      • GuildChannelManager#setPositions/RoleManager#setPositions (managers/GuildChannelManager.ts:92, managers/RoleManager.ts:158) take only absolute position, no discord.js-style relative entries.
      • GuildMemberRoleManager (managers/GuildMemberRoleManager.ts) hand-sorts via comparePositionTo in fetch()/fetchHighest()/fetchHoist()/etc. instead of reusing discordSort.
    5. transformResolved/MessageMentions follow-ups, both open in structures/messages/MessageMentions.ts:
      • No messages getter (resolved via client.messages) or attachments getter (built as Attachment).
      • parsedUsers/members only read from cache relations; discord.js also seeds uncached mentioned users/members from the raw payload — needs a decision, not just an implementation, since it changes what "resolved" means.

    Proposed plan

    Given the size, and that #157/#158 already sliced this tracking issue into digestible PRs, I'd split the remaining work the same way rather than one large PR:

    PR 1 — resolveGuildEmoji + file-name helper consolidation (items 1-2, small, low-risk)

    • Add resolveGuildEmoji(client, emojiId) to Util.ts, scanning cached guild emoji stores (or an ID index if one exists in plugin-cache) — needs a quick look at how plugin-cache exposes per-guild emoji listing today.
    • Replace MessagePayload's local nameOf() with Util.ts's basename/findName, verifying query-string stripping matches discord.js's behavior (check tests/serializers.test.ts for the existing contract before changing it).

    PR 2 — Decide and resolve flatten/resolveSKUId status (item 3, needs a decision first)

    • Confirm with the maintainer whether to keep these as public API surface with no internal callers (fine if they're meant for consumers), or drop them until something needs them. Recommend asking rather than guessing.

    PR 3 — Position follow-ups (item 4, the biggest chunk)

    • Computed sorted position (discord.js parity) with the raw value as rawPosition, resolved once in _hydrate per the pattern used elsewhere in the codebase (kRelations/kPatchRelations conventions).
    • A cache-side way to list a guild's channels/roles for sorting without a REST call — depends on whether plugin-cache already supports listing-by-guild-ID for these stores.
    • relative entries on both setPositions methods.
    • Refactor GuildMemberRoleManager to sort via discordSort instead of comparePositionTo.

    PR 4 — transformResolved/MessageMentions follow-ups (item 5)

    • Add messages and attachments getters to MessageMentions.
    • Decide and implement the raw-mention-inclusion policy for parsedUsers/members.

    I did not start implementation. Let me know if you'd like me to proceed with PR 1 (the smallest, least ambiguous slice) first.

    Pullfrog  | Implement plan ➔ | View workflow run | via Pullfrog | Using Claude Sonnet | 𝕏

  2. RedStar071 commented on Oct 17, 2026

    @RedStar071
    MemberAuthor

    #249 resolves the following from this issue (verified against current main; resolveGuildEmoji was already done in #236):

    • transformResolved now resolves messages (IDs via the new channelId option, raw data via client.messages) and attachments (built as Attachment). The Pullfrog plan put these on MessageMentions, but discord.js's MessageMentions has neither, so they live on transformResolved.
    • Raw mentioned users from the payload: decided and done. MessageMentions#parsedUsers now includes the users the payload mentions even when uncached (as discord.js does by caching them first), without writing the cache. Content-only mentions stay cache-only, as in discord.js.
    • MessagePayload's nameOf() already reuses basename; the URL handling stays local on purpose (findName would keep fragments and mis-name https://host/ and ?x=/a/b), now documented.
    • GuildMemberRoleManager keeps comparePositionTo: discordSort would not simplify it (extra copy plus reversing, and it tells roles from channels by a type heuristic).
    • flatten / resolveSKUId are kept as public util API.

    Still open:

    • A computed, sorted position for GuildChannel / Role, with the API value as rawPosition.
    • Sort the cached channels and roles of a guild without refetching them (fetchSorted, RoleManager#setPosition).
    • GuildChannelManager#setPositions / RoleManager#setPositions accepting relative entries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions