Skip to content

Commit

Permalink
feat: recurring scheduled events (#10447)
Browse files Browse the repository at this point in the history
* feat: recurring scheduled events

* fix: nullable on patch

* docs: remove unnecessary parenthesis

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>

---------

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
  • Loading branch information
2 people authored and Jiralite committed Sep 30, 2024
1 parent c122178 commit 97c3237
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 4 deletions.
24 changes: 24 additions & 0 deletions packages/discord.js/src/managers/GuildScheduledEventManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
const { resolveImage } = require('../util/DataResolver');
const { _transformGuildScheduledEventRecurrenceRule } = require('../util/Transformers');

/**
* Manages API methods for GuildScheduledEvents and stores their cache.
Expand Down Expand Up @@ -36,6 +37,21 @@ class GuildScheduledEventManager extends CachedManager {
* @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable
*/

/**
* 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
*/

/**
* Options used to create a guild scheduled event.
* @typedef {Object} GuildScheduledEventCreateOptions
Expand All @@ -54,6 +70,8 @@ class GuildScheduledEventManager extends CachedManager {
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for creating the guild scheduled event
* @property {GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
* The recurrence rule of the guild scheduled event
*/

/**
Expand Down Expand Up @@ -81,6 +99,7 @@ class GuildScheduledEventManager extends CachedManager {
entityMetadata,
reason,
image,
recurrenceRule,
} = options;

let entity_metadata, channel_id;
Expand All @@ -104,6 +123,7 @@ class GuildScheduledEventManager extends CachedManager {
entity_type: entityType,
entity_metadata,
image: image && (await resolveImage(image)),
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
},
reason,
});
Expand Down Expand Up @@ -178,6 +198,8 @@ class GuildScheduledEventManager extends CachedManager {
* {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for editing the guild scheduled event
* @property {?GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
* The recurrence rule of the guild scheduled event
*/

/**
Expand All @@ -203,6 +225,7 @@ class GuildScheduledEventManager extends CachedManager {
entityMetadata,
reason,
image,
recurrenceRule,
} = options;

let entity_metadata;
Expand All @@ -224,6 +247,7 @@ class GuildScheduledEventManager extends CachedManager {
status,
image: image && (await resolveImage(image)),
entity_metadata,
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
},
reason,
});
Expand Down
50 changes: 50 additions & 0 deletions packages/discord.js/src/structures/GuildScheduledEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,56 @@ class GuildScheduledEvent extends Base {
} else {
this.image ??= null;
}

/**
* Represents the recurrence rule for a {@link GuildScheduledEvent}.
* @typedef {Object} GuildScheduledEventRecurrenceRule
* @property {number} startTimestamp The timestamp the recurrence rule interval starts at
* @property {Date} startAt The time the recurrence rule interval starts at
* @property {?number} endTimestamp The timestamp the recurrence rule interval ends at
* @property {?Date} 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
*/

/**
* @typedef {Object} GuildScheduledEventRecurrenceRuleNWeekday
* @property {number} n The week to recur on
* @property {GuildScheduledEventRecurrenceRuleWeekday} day The day within the week to recur on
*/

if ('recurrence_rule' in data) {
/**
* The recurrence rule for this scheduled event
* @type {?GuildScheduledEventRecurrenceRule}
*/
this.recurrenceRule = {
startTimestamp: Date.parse(data.recurrence_rule.start),
get startAt() {
return new Date(this.startTimestamp);
},
endTimestamp: data.recurrence_rule.end && Date.parse(data.recurrence_rule.end),
get endAt() {
return this.endTimestamp && new Date(this.endTimestamp);
},
frequency: data.recurrence_rule.frequency,
interval: data.recurrence_rule.interval,
byWeekday: data.recurrence_rule.by_weekday,
byNWeekday: data.recurrence_rule.by_n_weekday,
byMonth: data.recurrence_rule.by_month,
byMonthDay: data.recurrence_rule.by_month_day,
byYearDay: data.recurrence_rule.by_year_day,
count: data.recurrence_rule.count,
};
} else {
this.recurrenceRule ??= null;
}
}

/**
Expand Down
20 changes: 20 additions & 0 deletions packages/discord.js/src/util/APITypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildMember}
*/

/**
* @external APIGuildScheduledEventRecurrenceRule
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildScheduledEventRecurrenceRule}
*/

/**
* @external APIInteraction
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction}
Expand Down Expand Up @@ -390,6 +395,21 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventPrivacyLevel}
*/

/**
* @external GuildScheduledEventRecurrenceRuleFrequency
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleFrequency}
*/

/**
* @external GuildScheduledEventRecurrenceRuleMonth
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleMonth}
*/

/**
* @external GuildScheduledEventRecurrenceRuleWeekday
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleWeekday}
*/

/**
* @external GuildScheduledEventStatus
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventStatus}
Expand Down
29 changes: 28 additions & 1 deletion packages/discord.js/src/util/Transformers.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,31 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
};
}

module.exports = { toSnakeCase, _transformAPIAutoModerationAction, _transformAPIMessageInteractionMetadata };
/**
* Transforms a guild scheduled event recurrence rule object to a snake-cased variant.
* @param {GuildScheduledEventRecurrenceRuleOptions} recurrenceRule The recurrence rule to transform
* @returns {APIGuildScheduledEventRecurrenceRule}
* @ignore
*/
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,
};
}

module.exports = {
toSnakeCase,
_transformAPIAutoModerationAction,
_transformAPIMessageInteractionMetadata,
_transformGuildScheduledEventRecurrenceRule,
};
5 changes: 3 additions & 2 deletions packages/discord.js/test/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

'use strict';

const { token } = require('./auth.js');
const { token, owner } = require('./auth.js');
const { Client } = require('../src');
const { ChannelType, GatewayIntentBits } = require('discord-api-types/v10');

Expand All @@ -14,6 +14,7 @@ const client = new Client({
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.MessageContent,
],
});

Expand Down Expand Up @@ -186,7 +187,7 @@ client.on('messageCreate', msg => {
msg.channel.send(`\`\`\`${msg.content}\`\`\``);
}

if (msg.content.startsWith('#eval') && msg.author.id === '66564597481480192') {
if (msg.content.startsWith('#eval') && msg.author.id === owner) {
try {
const com = eval(msg.content.split(' ').slice(1).join(' '));
msg.channel.send(`\`\`\`\n${com}\`\`\``);
Expand Down
41 changes: 40 additions & 1 deletion packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ import {
ReactionType,
APIAuthorizingIntegrationOwnersMap,
MessageReferenceType,
GuildScheduledEventRecurrenceRuleWeekday,
GuildScheduledEventRecurrenceRuleMonth,
GuildScheduledEventRecurrenceRuleFrequency,
} from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
Expand Down Expand Up @@ -1779,6 +1782,7 @@ export class GuildScheduledEvent<Status extends GuildScheduledEventStatus = Guil
public entityMetadata: GuildScheduledEventEntityMetadata | null;
public userCount: number | null;
public creator: User | null;
public recurrenceRule: GuildScheduledEventRecurrenceRule | null;
public get createdTimestamp(): number;
public get createdAt(): Date;
public get scheduledStartAt(): Date | null;
Expand Down Expand Up @@ -1817,6 +1821,26 @@ export class GuildScheduledEvent<Status extends GuildScheduledEventStatus = Guil
public isScheduled(): this is GuildScheduledEvent<GuildScheduledEventStatus.Scheduled>;
}

export interface GuildScheduledEventRecurrenceRule {
startTimestamp: number;
get startAt(): Date;
endTimestamp: number | null;
get endAt(): Date | null;
frequency: GuildScheduledEventRecurrenceRuleFrequency;
interval: number;
byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[] | null;
byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[] | null;
byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[] | null;
byMonthDay: readonly number[] | null;
byYearDay: readonly number[] | null;
count: number | null;
}

export interface GuildScheduledEventRecurrenceRuleNWeekday {
n: number;
day: GuildScheduledEventRecurrenceRuleWeekday;
}

export class GuildTemplate extends Base {
private constructor(client: Client<true>, data: RawGuildTemplateData);
public createdTimestamp: number;
Expand Down Expand Up @@ -6167,14 +6191,29 @@ export interface GuildScheduledEventCreateOptions {
entityMetadata?: GuildScheduledEventEntityMetadataOptions;
image?: BufferResolvable | Base64Resolvable | null;
reason?: string;
recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions;
}

export interface GuildScheduledEventRecurrenceRuleOptions {
startAt: DateResolvable;
endAt: DateResolvable;
frequency: GuildScheduledEventRecurrenceRuleFrequency;
interval: number;
byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[];
byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[];
byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[];
byMonthDay: readonly number[];
byYearDay: readonly number[];
count: number;
}

export interface GuildScheduledEventEditOptions<
Status extends GuildScheduledEventStatus,
AcceptableStatus extends GuildScheduledEventSetStatusArg<Status>,
> extends Omit<Partial<GuildScheduledEventCreateOptions>, 'channel'> {
> extends Omit<Partial<GuildScheduledEventCreateOptions>, 'channel' | 'recurrenceRule'> {
channel?: GuildVoiceChannelResolvable | null;
status?: AcceptableStatus;
recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions | null;
}

export interface GuildScheduledEventEntityMetadata {
Expand Down
4 changes: 4 additions & 0 deletions packages/discord.js/typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ import {
ApplicationEmoji,
ApplicationEmojiManager,
StickerPack,
GuildScheduledEventManager,
SendableChannels,
PollData,
} from '.';
Expand Down Expand Up @@ -2618,3 +2619,6 @@ client.on('interactionCreate', interaction => {
interaction.channel.send({ embeds: [] });
}
});

declare const guildScheduledEventManager: GuildScheduledEventManager;
await guildScheduledEventManager.edit(snowflake, { recurrenceRule: null });

0 comments on commit 97c3237

Please sign in to comment.