feat(structures): add Guild structures and additional bitfields to @discordjs/structures#11355
feat(structures): add Guild structures and additional bitfields to @discordjs/structures#11355
Conversation
📝 WalkthroughWalkthroughThis PR introduces comprehensive implementations for Discord guild-related structures and specialized bitfield classes. It adds four new bitfield wrapper classes (GuildMemberFlagsBitField, RoleFlagsBitField, UserFlagsBitField, SystemChannelFlagsBitField) and eight guild structure classes (Guild, GuildMember, Role, RoleTags, GuildEmoji, GuildBan, WelcomeChannel, WelcomeScreen) with full test coverage for both. All changes extend existing base classes and expose property getters mapping to underlying raw data. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Co-authored-by: iCrawl <20760160+iCrawl@users.noreply.github.com>
Co-authored-by: iCrawl <20760160+iCrawl@users.noreply.github.com>
Co-authored-by: iCrawl <20760160+iCrawl@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #11355 +/- ##
==========================================
+ Coverage 32.45% 33.12% +0.67%
==========================================
Files 369 381 +12
Lines 13612 13840 +228
Branches 1069 1081 +12
==========================================
+ Hits 4418 4585 +167
- Misses 9059 9108 +49
- Partials 135 147 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
packages/structures/__tests__/bitfields.test.ts(1 hunks)packages/structures/__tests__/guilds.test.ts(1 hunks)packages/structures/src/bitfields/GuildMemberFlagsBitField.ts(1 hunks)packages/structures/src/bitfields/RoleFlagsBitField.ts(1 hunks)packages/structures/src/bitfields/SystemChannelFlagsBitField.ts(1 hunks)packages/structures/src/bitfields/UserFlagsBitField.ts(1 hunks)packages/structures/src/bitfields/index.ts(1 hunks)packages/structures/src/guilds/Guild.ts(1 hunks)packages/structures/src/guilds/GuildBan.ts(1 hunks)packages/structures/src/guilds/GuildEmoji.ts(1 hunks)packages/structures/src/guilds/GuildMember.ts(1 hunks)packages/structures/src/guilds/Role.ts(1 hunks)packages/structures/src/guilds/RoleTags.ts(1 hunks)packages/structures/src/guilds/WelcomeChannel.ts(1 hunks)packages/structures/src/guilds/WelcomeScreen.ts(1 hunks)packages/structures/src/guilds/index.ts(1 hunks)packages/structures/src/index.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
packages/structures/src/bitfields/SystemChannelFlagsBitField.ts (1)
packages/discord.js/typings/index.d.ts (1)
BitField(663-680)
packages/structures/src/guilds/WelcomeScreen.ts (2)
packages/discord.js/typings/index.d.ts (1)
WelcomeScreen(3910-3916)packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/__tests__/guilds.test.ts (3)
packages/structures/src/utils/symbols.ts (1)
kData(1-1)packages/structures/src/guilds/GuildMember.ts (1)
joinedAt(75-78)packages/structures/src/guilds/RoleTags.ts (1)
RoleTags(11-65)
packages/structures/src/guilds/Role.ts (2)
packages/discord.js/typings/index.d.ts (1)
Role(2944-2984)packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/src/guilds/WelcomeChannel.ts (1)
packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/src/guilds/GuildBan.ts (2)
packages/discord.js/typings/index.d.ts (1)
GuildBan(1559-1566)packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/src/bitfields/UserFlagsBitField.ts (1)
packages/discord.js/typings/index.d.ts (1)
BitField(663-680)
packages/structures/src/bitfields/RoleFlagsBitField.ts (1)
packages/discord.js/typings/index.d.ts (1)
BitField(663-680)
packages/structures/src/guilds/RoleTags.ts (1)
packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/src/guilds/GuildMember.ts (2)
packages/structures/src/utils/symbols.ts (1)
kData(1-1)packages/structures/src/utils/optimization.ts (1)
dateToDiscordISOTimestamp(20-22)
packages/structures/src/guilds/Guild.ts (1)
packages/structures/src/utils/symbols.ts (1)
kData(1-1)
packages/structures/src/guilds/GuildEmoji.ts (2)
packages/discord.js/typings/index.d.ts (1)
GuildEmoji(1606-1619)packages/structures/src/utils/symbols.ts (1)
kData(1-1)
🔇 Additional comments (26)
packages/structures/src/guilds/Role.ts (2)
1-23: LGTM on class structure and imports.The abstract class follows the established pattern with proper generic constraints, DataTemplate, and constructor delegation to the base Structure class.
25-100: LGTM on property getters.All getters correctly map to their corresponding
APIRolefields viakData, following the established accessor pattern.packages/structures/src/guilds/GuildMember.ts (4)
1-27: LGTM on imports, type definitions, and DataTemplate.The
GuildMemberOmittedKeystype correctly identifies the timestamp fields to be optimized. TheDataTemplatewith no-op setters is a clever pattern to prevent storing raw timestamp strings while allowing the optimization logic to handle them.
29-35: LGTM on constructor.The constructor correctly delegates to
super()then callsoptimizeData()to convert ISO timestamp strings to numeric values.
37-154: LGTM on property getters.The getters correctly handle the optimized timestamp fields with appropriate type assertions. The
isCommunicationDisabledlogic properly checks that the timestamp exists and is in the future.
156-174: LGTM onoptimizeDataimplementation.The method correctly handles the nullable timestamp fields by checking both
undefinedandnullbefore conversion, and stores the numeric timestamps in appropriately named internal fields.packages/structures/src/guilds/Guild.ts (2)
1-23: LGTM on class structure.The abstract
Guildclass follows the established pattern with proper documentation noting the substructures that extending classes need to handle.
25-268: LGTM on property getters.All 30+ getters correctly map to their corresponding
APIGuildfields with appropriate camelCase naming conventions.packages/structures/src/index.ts (1)
3-3: LGTM on guilds module export.The re-export follows the established pattern and maintains alphabetical ordering.
packages/structures/__tests__/guilds.test.ts (5)
1-32: LGTM on imports.All necessary types and structures are imported correctly for comprehensive testing.
34-115: LGTM on Guild tests.Good coverage of essential properties. The
as anycast on line 64 forpreferred_localeis acceptable for test data.
194-247: LGTM on Role tests.Good coverage including the
hexColorcomputed property verification.
249-288: LGTM on RoleTags tests.Good coverage of the various tag types and their presence-based boolean semantics.
290-395: LGTM on remaining structure tests.GuildEmoji, GuildBan, WelcomeChannel, and WelcomeScreen tests provide adequate coverage of their respective properties.
packages/structures/src/bitfields/GuildMemberFlagsBitField.ts (1)
1-16: GuildMemberFlagsBitField is consistent with existing BitField wrappersThe generic constraint, static
Flagsbinding, andtoJSONoverride all align with the expected pattern for enum-based bitfields. No issues from a correctness or API-shape perspective.packages/structures/src/bitfields/SystemChannelFlagsBitField.ts (1)
1-16: SystemChannelFlagsBitField matches the shared BitField wrapper patternImplementation cleanly wires
GuildSystemChannelFlagsintoBitFieldwith a numerictoJSONoverride, consistent with the other bitfield helpers. No changes requested.packages/structures/src/bitfields/index.ts (1)
3-10: New bitfield exports are correctly wired into the public surfaceThe additional exports cleanly expose the new bitfield classes alongside existing ones without changing prior behavior.
packages/structures/src/bitfields/UserFlagsBitField.ts (1)
1-16: UserFlagsBitField is a clean, symmetric wrapperThis follows the same pattern as the other flag bitfields (generic key type, static
Flags, numerictoJSON), keeping the API consistent. Looks good.packages/structures/src/bitfields/RoleFlagsBitField.ts (1)
1-16: RoleFlagsBitField correctly wires RoleFlags into BitFieldThe class shape and
toJSONoverride match the other flag wrappers, so behavior should be predictable and uniform across bitfield types.packages/structures/src/guilds/GuildBan.ts (1)
1-31: GuildBan base structure is minimal and extensible as intendedThe abstract
GuildBancleanly forwards raw API data intoStructure, exposesreasonfromkData, and leavesDataTemplateplus theUsersubstructure to concrete subclasses. This matches the documented design and doesn’t introduce obvious type or runtime issues.packages/structures/src/guilds/index.ts (1)
1-8: Guild structures barrel cleanly exposes the new typesThe re-exports are comprehensive and make the guild-related structures easy to import without affecting existing consumers.
packages/structures/src/guilds/WelcomeChannel.ts (1)
27-53: No changes needed —emojiNameandemojiIdnullability is already correctThe underlying API fields
emoji_idandemoji_nameare always present (not optional) and are nullable (Snowflake | nullandstring | nullrespectively). They returnnulldirectly when the API providesnull, so the JSDoc contract of "null if no emoji is set" is already fulfilled. No?? nullnormalization is required; the current implementation is correct.packages/structures/src/guilds/WelcomeScreen.ts (1)
6-34: LGTM!The abstract class correctly implements the Structure pattern with proper generic constraints. The documentation clearly indicates that
WelcomeChannelsubstructures need to be instantiated by extending classes, which is an appropriate design for maintaining flexibility.packages/structures/src/guilds/GuildEmoji.ts (1)
6-73: LGTM!The abstract class correctly wraps
APIEmojidata with properly documented getters. The snake_case to camelCase mapping (e.g.,require_colons→requireColons) follows TypeScript conventions. The documentation appropriately notes that theUsersubstructure needs to be handled by extending classes.packages/structures/src/guilds/RoleTags.ts (1)
38-64: LGTM!The boolean getters (
premiumSubscriber,availableForPurchase,guildConnections) correctly implement Discord's sentinel field pattern where the presence of the key (regardless of value) indicatestrue. Using!== undefinedproperly handles this API behavior.packages/structures/__tests__/bitfields.test.ts (1)
1-204: Good test coverage for bitfield operations.The test suite comprehensively covers the core operations (construction, add, remove, has, serialize, toJSON) for all four bitfield classes. The tests correctly use the flag enums from
discord-api-types/v10.
| test('should correctly identify if communication is disabled', () => { | ||
| // The timestamp is in the future (2024-12-31), so it should be disabled | ||
| // But we're testing in 2025, so let's check the behavior | ||
| expect(typeof member.isCommunicationDisabled).toBe('boolean'); | ||
| }); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Time-sensitive test may produce unexpected results.
The comment acknowledges that the test data uses a 2024 timestamp, but the test only checks typeof. Given the current date is December 2025, isCommunicationDisabled will always be false since the timeout has expired.
Consider either using a far-future date or explicitly asserting the expected false value:
test('should correctly identify if communication is disabled', () => {
- // The timestamp is in the future (2024-12-31), so it should be disabled
- // But we're testing in 2025, so let's check the behavior
- expect(typeof member.isCommunicationDisabled).toBe('boolean');
+ // The timestamp (2024-12-31) is in the past, so communication is not disabled
+ expect(member.isCommunicationDisabled).toBe(false);
});Or use a far-future timestamp in the test data to ensure consistent behavior.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test('should correctly identify if communication is disabled', () => { | |
| // The timestamp is in the future (2024-12-31), so it should be disabled | |
| // But we're testing in 2025, so let's check the behavior | |
| expect(typeof member.isCommunicationDisabled).toBe('boolean'); | |
| }); | |
| test('should correctly identify if communication is disabled', () => { | |
| // The timestamp (2024-12-31) is in the past, so communication is not disabled | |
| expect(member.isCommunicationDisabled).toBe(false); | |
| }); |
🤖 Prompt for AI Agents
In packages/structures/__tests__/guilds.test.ts around lines 180 to 184, the
test uses a 2024 timestamp and only asserts typeof
member.isCommunicationDisabled, which is time-sensitive and will be false in
2025; either update the fixture to use a far-future timestamp (e.g., year 9999)
so isCommunicationDisabled is consistently true and assert the expected boolean,
or explicitly assert the current expected value
(expect(member.isCommunicationDisabled).toBe(false)) to reflect the expired
timeout.
| public override toJSON(): APIGuildMember { | ||
| const data = { ...this[kData] } as APIGuildMember; | ||
|
|
||
| const joinedTimestamp = this.joinedTimestamp; | ||
| if (joinedTimestamp !== null) { | ||
| data.joined_at = dateToDiscordISOTimestamp(new Date(joinedTimestamp)); | ||
| } | ||
|
|
||
| const premiumSinceTimestamp = this.premiumSinceTimestamp; | ||
| if (premiumSinceTimestamp !== null) { | ||
| data.premium_since = dateToDiscordISOTimestamp(new Date(premiumSinceTimestamp)); | ||
| } | ||
|
|
||
| const communicationDisabledUntilTimestamp = this.communicationDisabledUntilTimestamp; | ||
| if (communicationDisabledUntilTimestamp !== null) { | ||
| data.communication_disabled_until = dateToDiscordISOTimestamp(new Date(communicationDisabledUntilTimestamp)); | ||
| } | ||
|
|
||
| return data; | ||
| } |
There was a problem hiding this comment.
Internal timestamp fields leak into JSON output.
The spread { ...this[kData] } copies the internal *_timestamp fields (e.g., joined_at_timestamp) into the JSON output. These aren't part of APIGuildMember and may cause issues if the JSON is sent back to Discord's API or compared for equality.
Consider deleting the internal fields from the output:
public override toJSON(): APIGuildMember {
const data = { ...this[kData] } as APIGuildMember;
+ // Remove internal optimization fields
+ delete (data as Record<string, unknown>).joined_at_timestamp;
+ delete (data as Record<string, unknown>).premium_since_timestamp;
+ delete (data as Record<string, unknown>).communication_disabled_until_timestamp;
const joinedTimestamp = this.joinedTimestamp;
if (joinedTimestamp !== null) {
data.joined_at = dateToDiscordISOTimestamp(new Date(joinedTimestamp));
}🤖 Prompt for AI Agents
In packages/structures/src/guilds/GuildMember.ts around lines 176 to 195, the
spread of this[kData] into the APIGuildMember output leaks internal timestamp
fields (e.g., joined_at_timestamp, premium_since_timestamp,
communication_disabled_until_timestamp) that are not part of APIGuildMember;
remove those internal keys from the copied data object after spreading and
before returning (delete the *_timestamp properties or filter them out) so only
the correct API fields (joined_at, premium_since, communication_disabled_until)
remain in the returned JSON.
| public get hexColor() { | ||
| const color = this.color as number; | ||
| return `#${color.toString(16).padStart(6, '0')}`; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Unnecessary type assertion.
The color getter already returns a number from APIRole, so the as number cast is redundant.
public get hexColor() {
- const color = this.color as number;
- return `#${color.toString(16).padStart(6, '0')}`;
+ return `#${this.color.toString(16).padStart(6, '0')}`;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public get hexColor() { | |
| const color = this.color as number; | |
| return `#${color.toString(16).padStart(6, '0')}`; | |
| } | |
| public get hexColor() { | |
| return `#${this.color.toString(16).padStart(6, '0')}`; | |
| } |
🤖 Prompt for AI Agents
In packages/structures/src/guilds/Role.ts around lines 105 to 108, remove the
redundant "as number" type assertion in the hexColor getter; assign const color
= this.color; and keep the rest of the logic unchanged (format color to hex with
toString(16).padStart(6, '0') and prefix with '#').
Implements Sprint 1 of the greenfield rewrite: core Guild-related structures and foundational bitfields for bot functionality.
Structures Added (
packages/structures/src/guilds/)Bitfields Added (
packages/structures/src/bitfields/)Implementation Notes
abstractwhere substructures (User, AvatarDecorationData) require instantiation by extending classesUsage Example
Testing
Original prompt
Overview
As part of the greenfield rewrite plan for discord.js, we need to expand the
@discordjs/structurespackage with missing entity structures. This PR focuses on Sprint 1: adding Guild-related structures and additional bitfields that are foundational for most bot functionality.Current State
The
packages/structurespackage already has a solid foundation with:Structureclass withkData,kPatch,kClone, data templates, and optimization supportMixinsystem for composing structuresBitField,PermissionsBitField,MessageFlagsBitField,ChannelFlagsBitField,AttachmentFlagsBitField)Structures to Implement
Guild-related Structures (Priority)
Guild- Core guild/server structureid,name,icon,splash,discoverySplash,ownerId,afkChannelId,afkTimeout,widgetEnabled,widgetChannelId,verificationLevel,defaultMessageNotifications,explicitContentFilter,features,mfaLevel,applicationId,systemChannelId,systemChannelFlags,rulesChannelId,maxPresences,maxMembers,vanityUrlCode,description,banner,premiumTier,premiumSubscriptionCount,preferredLocale,publicUpdatesChannelId,maxVideoChannelUsers,maxStageVideoChannelUsers,approximateMemberCount,approximatePresenceCount,welcomeScreen,nsfwLevel,premiumProgressBarEnabled,safetyAlertsChannelIdAPIGuildfromdiscord-api-typesGuildMember- Guild member structureuserId,nick,avatar,roles,joinedAt,premiumSince,deaf,mute,flags,pending,permissions,communicationDisabledUntil,avatarDecorationDataAPIGuildMemberfromdiscord-api-typesRole- Role structureid,name,color,hoist,icon,unicodeEmoji,position,permissions,managed,mentionable,tags,flagsAPIRolefromdiscord-api-typesGuildEmoji- Guild emoji structureid,name,roles,user,requireColons,managed,animated,availableAPIEmojifromdiscord-api-typesGuildBan- Guild ban structurereason,userAPIBanfromdiscord-api-typesRoleTags- Role tags structurebotId,integrationId,premiumSubscriber,subscriptionListingId,availableForPurchase,guildConnectionsAPIRoleTagsfromdiscord-api-typesWelcomeScreen- Guild welcome screen structuredescription,welcomeChannelsAPIGuildWelcomeScreenfromdiscord-api-typesWelcomeChannel- Welcome screen channel structurechannelId,description,emojiId,emojiNameAPIGuildWelcomeScreenChannelfromdiscord-api-typesAdditional Bitfields
GuildMemberFlagsBitField- UsingGuildMemberFlagsfromdiscord-api-typesRoleFlagsBitField- UsingRoleFlagsfromdiscord-api-typesUserFlagsBitField- UsingUserFlagsfromdiscord-api-typesSystemChannelFlagsBitField- UsingGuildSystemChannelFlagsfromdiscord-api-typesGuildFlagsBitField- UsingGuildFeatureflags (if applicable)Implementation Guidelines
packages/structures/src/Structurebase class - All structures should extendStructure<APIType, OmittedKeys>DataTemplatefor optimized data storage where appropriateoptimizeData- For timestamp fields and other optimizable datadiscord-api-types- Import types fromdiscord-api-types/v10packages/structures/src/index.tsguilds/folder - Organize guild structures inpackages/structures/src/guilds/File Structure
Example Implementation Pattern
Based on existing structures like
User.ts: