Skip to content

Commit

Permalink
Merge branch 'main' into build/no-test-run
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Jan 26, 2025
2 parents b286863 + b820daa commit ddc96f6
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 70 deletions.
55 changes: 55 additions & 0 deletions packages/collection/__tests__/collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,3 +1099,58 @@ describe('findLastKey() tests', () => {
}, null);
});
});

describe('subclassing tests', () => {
class DerivedCollection<Key, Value> extends Collection<Key, Value> {}

test('constructor[Symbol.species]', () => {
expect(DerivedCollection[Symbol.species]).toStrictEqual(DerivedCollection);
});

describe('methods that construct new collections return subclassed objects', () => {
const coll = new DerivedCollection();

test('filter()', () => {
expect(coll.filter(Boolean)).toBeInstanceOf(DerivedCollection);
});
test('partition()', () => {
for (const partition of coll.partition(Boolean)) {
expect(partition).toBeInstanceOf(DerivedCollection);
}
});
test('flatMap()', () => {
expect(coll.flatMap(() => new Collection())).toBeInstanceOf(DerivedCollection);
});
test('mapValues()', () => {
expect(coll.mapValues(Object)).toBeInstanceOf(DerivedCollection);
});
test('clone()', () => {
expect(coll.clone()).toBeInstanceOf(DerivedCollection);
});
test('intersection()', () => {
expect(coll.intersection(new Collection())).toBeInstanceOf(DerivedCollection);
});
test('union()', () => {
expect(coll.union(new Collection())).toBeInstanceOf(DerivedCollection);
});
test('difference()', () => {
expect(coll.difference(new Collection())).toBeInstanceOf(DerivedCollection);
});
test('symmetricDifference()', () => {
expect(coll.symmetricDifference(new Collection())).toBeInstanceOf(DerivedCollection);
});
test('merge()', () => {
const fn = () => ({ keep: false }) as const; // eslint-disable-line unicorn/consistent-function-scoping
expect(coll.merge(new Collection(), fn, fn, fn)).toBeInstanceOf(DerivedCollection);
});
test('toReversed()', () => {
expect(coll.toReversed()).toBeInstanceOf(DerivedCollection);
});
test('toSorted()', () => {
expect(coll.toSorted()).toBeInstanceOf(DerivedCollection);
});
test('Collection.combineEntries()', () => {
expect(DerivedCollection.combineEntries([], Object)).toBeInstanceOf(DerivedCollection);
});
});
});
11 changes: 8 additions & 3 deletions packages/collection/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export type ReadonlyCollection<Key, Value> = Omit<

export interface Collection<Key, Value> {
/**
* Ambient declaration to allow `this.constructor[@@species]` in class methods.
* Ambient declaration to allow references to `this.constructor` in class methods.
*
* @internal
*/
constructor: typeof Collection & { readonly [Symbol.species]: typeof Collection };
constructor: typeof Collection;
}

/**
Expand Down Expand Up @@ -1076,7 +1076,7 @@ export class Collection<Key, Value> extends Map<Key, Value> {
entries: Iterable<[Key, Value]>,
combine: (firstValue: Value, secondValue: Value, key: Key) => Value,
): Collection<Key, Value> {
const coll = new Collection<Key, Value>();
const coll = new this[Symbol.species]<Key, Value>();
for (const [key, value] of entries) {
if (coll.has(key)) {
coll.set(key, combine(coll.get(key)!, value, key));
Expand All @@ -1087,6 +1087,11 @@ export class Collection<Key, Value> extends Map<Key, Value> {

return coll;
}

/**
* @internal
*/
declare public static readonly [Symbol.species]: typeof Collection;
}

/**
Expand Down
46 changes: 34 additions & 12 deletions packages/discord.js/src/managers/ApplicationCommandManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ class ApplicationCommandManager extends CachedManager {
/**
* Options used to fetch Application Commands from Discord
* @typedef {BaseFetchOptions} FetchApplicationCommandOptions
* @property {Snowflake} [id] The command's id to fetch
* @property {Snowflake} [guildId] The guild's id to fetch commands for, for when the guild is not cached
* @property {Locale} [locale] The locale to use when fetching this command
* @property {boolean} [withLocalizations] Whether to fetch all localization data
*/

/**
* Obtains one or multiple application commands from Discord, or the cache if it's already available.
* @param {Snowflake|FetchApplicationCommandOptions} [id] Options for fetching application command(s)
* @param {FetchApplicationCommandOptions} [options] Additional options for this fetch
* @param {Snowflake|FetchApplicationCommandOptions} [options] Options for fetching application command(s)
* @returns {Promise<ApplicationCommand|Collection<Snowflake, ApplicationCommand>>}
* @example
* // Fetch a single command
Expand All @@ -98,28 +98,50 @@ class ApplicationCommandManager extends CachedManager {
* .catch(console.error);
* @example
* // Fetch all commands
* client.application.commands.fetch()
* .then(commands => console.log(`Fetched ${commands.size} commands`))
* .catch(console.error);
* @example
* // Fetch all commands in a guild
* guild.commands.fetch()
* .then(commands => console.log(`Fetched ${commands.size} commands`))
* .catch(console.error);
* @example
* // Fetch a single command without checking cache
* guild.commands.fetch({ id: '123456789012345678', force: true })
* .then(command => console.log(`Fetched command ${command.name}`))
* .catch(console.error)
*/
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
if (typeof id === 'object') {
({ guildId, cache = true, locale, withLocalizations } = id);
} else if (id) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const command = await this.client.rest.get(this.commandPath({ id, guildId }));
return this._add(command, cache);
async fetch(options) {
if (!options) return this._fetchMany();

if (typeof options === 'string') return this._fetchSingle({ id: options });

const { cache, force, guildId, id, locale, withLocalizations } = options;

if (id) return this._fetchSingle({ cache, force, guildId, id });

return this._fetchMany({ cache, guildId, locale, withLocalizations });
}

async _fetchSingle({ cache, force = false, guildId, id }) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}

const command = await this.client.rest.get(this.commandPath({ id, guildId }));
return this._add(command, cache);
}

async _fetchMany({ cache, guildId, locale, withLocalizations } = {}) {
const data = await this.client.rest.get(this.commandPath({ guildId }), {
headers: {
'X-Discord-Locale': locale,
},
query: makeURLSearchParams({ with_localizations: withLocalizations }),
});

return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,12 @@ class GuildScheduledEventManager extends CachedManager {
* Options for setting a recurrence rule for a guild scheduled event.
* @typedef {Object} GuildScheduledEventRecurrenceRuleOptions
* @property {DateResolvable} startAt The time the recurrence rule interval starts at
* @property {?DateResolvable} endAt The time the recurrence rule interval ends at
* @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs
* @property {number} interval The spacing between the events
* @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on
* @property {?number[]} byMonthDay The days within a month to recur on
* @property {?number[]} byYearDay The days within a year to recur on
* @property {?number} count The total amount of times the event is allowed to recur before stopping
*/

/**
Expand Down
40 changes: 25 additions & 15 deletions packages/discord.js/src/structures/GuildAuditLogsEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ const Targets = {
Thread: 'Thread',
ApplicationCommand: 'ApplicationCommand',
AutoModeration: 'AutoModeration',
GuildOnboarding: 'GuildOnboarding',
GuildOnboardingPrompt: 'GuildOnboardingPrompt',
SoundboardSound: 'SoundboardSound',
Unknown: 'Unknown',
};

// TODO: Add soundboard sounds when https://github.com/discordjs/discord.js/pull/10590 is merged
/**
* The target of a guild audit log entry. It can be one of:
* * A guild
Expand All @@ -42,8 +43,7 @@ const Targets = {
* * A role
* * An invite
* * A webhook
* * An emoji
* * A message
* * A guild emoji
* * An integration
* * A stage instance
* * A sticker
Expand All @@ -54,7 +54,7 @@ const Targets = {
* * A guild onboarding prompt
* * An object with an id key if target was deleted or fake entity
* * An object where the keys represent either the new value or the old value
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker|
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Integration|StageInstance|Sticker|
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt)} AuditLogEntryTarget
*/

Expand Down Expand Up @@ -82,9 +82,10 @@ const Targets = {
* * Sticker
* * Thread
* * GuildScheduledEvent
* * ApplicationCommandPermission
* * GuildOnboarding
* * ApplicationCommand
* * GuildOnboardingPrompt
* * SoundboardSound
* * AutoModeration
* * Unknown
* @typedef {string} AuditLogTargetType
*/
Expand Down Expand Up @@ -198,7 +199,6 @@ class GuildAuditLogsEntry {

case AuditLogEvent.MemberMove:
case AuditLogEvent.MessageDelete:
case AuditLogEvent.MessageBulkDelete:
this.extra = {
channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
count: Number(data.options.count),
Expand All @@ -213,6 +213,7 @@ class GuildAuditLogsEntry {
};
break;

case AuditLogEvent.MessageBulkDelete:
case AuditLogEvent.MemberDisconnect:
this.extra = {
count: Number(data.options.count),
Expand Down Expand Up @@ -364,10 +365,15 @@ class GuildAuditLogsEntry {
data.action_type === AuditLogEvent.OnboardingPromptCreate
? new GuildOnboardingPrompt(guild.client, changesReduce(this.changes, { id: data.target_id }), guild.id)
: changesReduce(this.changes, { id: data.target_id });
} else if (targetType === Targets.GuildOnboarding) {
this.target = changesReduce(this.changes, { id: data.target_id });
} else if (targetType === Targets.Role) {
this.target = guild.roles.cache.get(data.target_id) ?? { id: data.target_id };
} else if (targetType === Targets.Emoji) {
this.target = guild.emojis.cache.get(data.target_id) ?? { id: data.target_id };
// TODO: Uncomment after https://github.com/discordjs/discord.js/pull/10590 is merged
// } else if (targetType === Targets.SoundboardSound) {
// this.target = guild.soundboardSounds.cache.get(data.target_id) ?? { id: data.target_id };
} else if (data.target_id) {
this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id };
this.target = { id: data.target_id };
}
}

Expand All @@ -391,9 +397,10 @@ class GuildAuditLogsEntry {
if (target < 110) return Targets.GuildScheduledEvent;
if (target < 120) return Targets.Thread;
if (target < 130) return Targets.ApplicationCommand;
if (target >= 140 && target < 150) return Targets.AutoModeration;
if (target < 140) return Targets.SoundboardSound;
if (target < 143) return Targets.AutoModeration;
if (target < 146) return Targets.User;
if (target >= 163 && target <= 165) return Targets.GuildOnboardingPrompt;
if (target >= 160 && target < 170) return Targets.GuildOnboarding;
return Targets.Unknown;
}

Expand All @@ -419,10 +426,9 @@ class GuildAuditLogsEntry {
AuditLogEvent.StickerCreate,
AuditLogEvent.GuildScheduledEventCreate,
AuditLogEvent.ThreadCreate,
AuditLogEvent.SoundboardSoundCreate,
AuditLogEvent.AutoModerationRuleCreate,
AuditLogEvent.AutoModerationBlockMessage,
AuditLogEvent.OnboardingPromptCreate,
AuditLogEvent.OnboardingCreate,
].includes(action)
) {
return 'Create';
Expand All @@ -448,6 +454,7 @@ class GuildAuditLogsEntry {
AuditLogEvent.StickerDelete,
AuditLogEvent.GuildScheduledEventDelete,
AuditLogEvent.ThreadDelete,
AuditLogEvent.SoundboardSoundDelete,
AuditLogEvent.AutoModerationRuleDelete,
AuditLogEvent.OnboardingPromptDelete,
].includes(action)
Expand All @@ -473,9 +480,12 @@ class GuildAuditLogsEntry {
AuditLogEvent.GuildScheduledEventUpdate,
AuditLogEvent.ThreadUpdate,
AuditLogEvent.ApplicationCommandPermissionUpdate,
AuditLogEvent.SoundboardSoundUpdate,
AuditLogEvent.AutoModerationRuleUpdate,
AuditLogEvent.AutoModerationBlockMessage,
AuditLogEvent.AutoModerationFlagToChannel,
AuditLogEvent.AutoModerationUserCommunicationDisabled,
AuditLogEvent.OnboardingPromptUpdate,
AuditLogEvent.OnboardingUpdate,
].includes(action)
) {
return 'Update';
Expand Down
4 changes: 0 additions & 4 deletions packages/discord.js/src/util/Transformers.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,12 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
function _transformGuildScheduledEventRecurrenceRule(recurrenceRule) {
return {
start: new Date(recurrenceRule.startAt).toISOString(),
// eslint-disable-next-line eqeqeq
end: recurrenceRule.endAt != null ? new Date(recurrenceRule.endAt).toISOString() : recurrenceRule.endAt,
frequency: recurrenceRule.frequency,
interval: recurrenceRule.interval,
by_weekday: recurrenceRule.byWeekday,
by_n_weekday: recurrenceRule.byNWeekday,
by_month: recurrenceRule.byMonth,
by_month_day: recurrenceRule.byMonthDay,
by_year_day: recurrenceRule.byYearDay,
count: recurrenceRule.count,
};
}

Expand Down
Loading

0 comments on commit ddc96f6

Please sign in to comment.