Skip to content

Commit 97c3237

Browse files
almeidxvladfrangu
authored andcommitted
feat: recurring scheduled events (#10447)
* feat: recurring scheduled events * fix: nullable on patch * docs: remove unnecessary parenthesis Co-authored-by: Vlad Frangu <[email protected]> --------- Co-authored-by: Vlad Frangu <[email protected]>
1 parent c122178 commit 97c3237

File tree

7 files changed

+169
-4
lines changed

7 files changed

+169
-4
lines changed

packages/discord.js/src/managers/GuildScheduledEventManager.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const CachedManager = require('./CachedManager');
77
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
88
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
99
const { resolveImage } = require('../util/DataResolver');
10+
const { _transformGuildScheduledEventRecurrenceRule } = require('../util/Transformers');
1011

1112
/**
1213
* Manages API methods for GuildScheduledEvents and stores their cache.
@@ -36,6 +37,21 @@ class GuildScheduledEventManager extends CachedManager {
3637
* @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable
3738
*/
3839

40+
/**
41+
* Options for setting a recurrence rule for a guild scheduled event.
42+
* @typedef {Object} GuildScheduledEventRecurrenceRuleOptions
43+
* @property {DateResolvable} startAt The time the recurrence rule interval starts at
44+
* @property {?DateResolvable} endAt The time the recurrence rule interval ends at
45+
* @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs
46+
* @property {number} interval The spacing between the events
47+
* @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on
48+
* @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on
49+
* @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on
50+
* @property {?number[]} byMonthDay The days within a month to recur on
51+
* @property {?number[]} byYearDay The days within a year to recur on
52+
* @property {?number} count The total amount of times the event is allowed to recur before stopping
53+
*/
54+
3955
/**
4056
* Options used to create a guild scheduled event.
4157
* @typedef {Object} GuildScheduledEventCreateOptions
@@ -54,6 +70,8 @@ class GuildScheduledEventManager extends CachedManager {
5470
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
5571
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
5672
* @property {string} [reason] The reason for creating the guild scheduled event
73+
* @property {GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
74+
* The recurrence rule of the guild scheduled event
5775
*/
5876

5977
/**
@@ -81,6 +99,7 @@ class GuildScheduledEventManager extends CachedManager {
8199
entityMetadata,
82100
reason,
83101
image,
102+
recurrenceRule,
84103
} = options;
85104

86105
let entity_metadata, channel_id;
@@ -104,6 +123,7 @@ class GuildScheduledEventManager extends CachedManager {
104123
entity_type: entityType,
105124
entity_metadata,
106125
image: image && (await resolveImage(image)),
126+
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
107127
},
108128
reason,
109129
});
@@ -178,6 +198,8 @@ class GuildScheduledEventManager extends CachedManager {
178198
* {@link GuildScheduledEventEntityType.External}</warn>
179199
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
180200
* @property {string} [reason] The reason for editing the guild scheduled event
201+
* @property {?GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
202+
* The recurrence rule of the guild scheduled event
181203
*/
182204

183205
/**
@@ -203,6 +225,7 @@ class GuildScheduledEventManager extends CachedManager {
203225
entityMetadata,
204226
reason,
205227
image,
228+
recurrenceRule,
206229
} = options;
207230

208231
let entity_metadata;
@@ -224,6 +247,7 @@ class GuildScheduledEventManager extends CachedManager {
224247
status,
225248
image: image && (await resolveImage(image)),
226249
entity_metadata,
250+
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
227251
},
228252
reason,
229253
});

packages/discord.js/src/structures/GuildScheduledEvent.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,56 @@ class GuildScheduledEvent extends Base {
189189
} else {
190190
this.image ??= null;
191191
}
192+
193+
/**
194+
* Represents the recurrence rule for a {@link GuildScheduledEvent}.
195+
* @typedef {Object} GuildScheduledEventRecurrenceRule
196+
* @property {number} startTimestamp The timestamp the recurrence rule interval starts at
197+
* @property {Date} startAt The time the recurrence rule interval starts at
198+
* @property {?number} endTimestamp The timestamp the recurrence rule interval ends at
199+
* @property {?Date} endAt The time the recurrence rule interval ends at
200+
* @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs
201+
* @property {number} interval The spacing between the events
202+
* @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on
203+
* @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on
204+
* @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on
205+
* @property {?number[]} byMonthDay The days within a month to recur on
206+
* @property {?number[]} byYearDay The days within a year to recur on
207+
* @property {?number} count The total amount of times the event is allowed to recur before stopping
208+
*/
209+
210+
/**
211+
* @typedef {Object} GuildScheduledEventRecurrenceRuleNWeekday
212+
* @property {number} n The week to recur on
213+
* @property {GuildScheduledEventRecurrenceRuleWeekday} day The day within the week to recur on
214+
*/
215+
216+
if ('recurrence_rule' in data) {
217+
/**
218+
* The recurrence rule for this scheduled event
219+
* @type {?GuildScheduledEventRecurrenceRule}
220+
*/
221+
this.recurrenceRule = {
222+
startTimestamp: Date.parse(data.recurrence_rule.start),
223+
get startAt() {
224+
return new Date(this.startTimestamp);
225+
},
226+
endTimestamp: data.recurrence_rule.end && Date.parse(data.recurrence_rule.end),
227+
get endAt() {
228+
return this.endTimestamp && new Date(this.endTimestamp);
229+
},
230+
frequency: data.recurrence_rule.frequency,
231+
interval: data.recurrence_rule.interval,
232+
byWeekday: data.recurrence_rule.by_weekday,
233+
byNWeekday: data.recurrence_rule.by_n_weekday,
234+
byMonth: data.recurrence_rule.by_month,
235+
byMonthDay: data.recurrence_rule.by_month_day,
236+
byYearDay: data.recurrence_rule.by_year_day,
237+
count: data.recurrence_rule.count,
238+
};
239+
} else {
240+
this.recurrenceRule ??= null;
241+
}
192242
}
193243

194244
/**

packages/discord.js/src/util/APITypes.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@
100100
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildMember}
101101
*/
102102

103+
/**
104+
* @external APIGuildScheduledEventRecurrenceRule
105+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildScheduledEventRecurrenceRule}
106+
*/
107+
103108
/**
104109
* @external APIInteraction
105110
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction}
@@ -390,6 +395,21 @@
390395
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventPrivacyLevel}
391396
*/
392397

398+
/**
399+
* @external GuildScheduledEventRecurrenceRuleFrequency
400+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleFrequency}
401+
*/
402+
403+
/**
404+
* @external GuildScheduledEventRecurrenceRuleMonth
405+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleMonth}
406+
*/
407+
408+
/**
409+
* @external GuildScheduledEventRecurrenceRuleWeekday
410+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleWeekday}
411+
*/
412+
393413
/**
394414
* @external GuildScheduledEventStatus
395415
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventStatus}

packages/discord.js/src/util/Transformers.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,31 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
5454
};
5555
}
5656

57-
module.exports = { toSnakeCase, _transformAPIAutoModerationAction, _transformAPIMessageInteractionMetadata };
57+
/**
58+
* Transforms a guild scheduled event recurrence rule object to a snake-cased variant.
59+
* @param {GuildScheduledEventRecurrenceRuleOptions} recurrenceRule The recurrence rule to transform
60+
* @returns {APIGuildScheduledEventRecurrenceRule}
61+
* @ignore
62+
*/
63+
function _transformGuildScheduledEventRecurrenceRule(recurrenceRule) {
64+
return {
65+
start: new Date(recurrenceRule.startAt).toISOString(),
66+
// eslint-disable-next-line eqeqeq
67+
end: recurrenceRule.endAt != null ? new Date(recurrenceRule.endAt).toISOString() : recurrenceRule.endAt,
68+
frequency: recurrenceRule.frequency,
69+
interval: recurrenceRule.interval,
70+
by_weekday: recurrenceRule.byWeekday,
71+
by_n_weekday: recurrenceRule.byNWeekday,
72+
by_month: recurrenceRule.byMonth,
73+
by_month_day: recurrenceRule.byMonthDay,
74+
by_year_day: recurrenceRule.byYearDay,
75+
count: recurrenceRule.count,
76+
};
77+
}
78+
79+
module.exports = {
80+
toSnakeCase,
81+
_transformAPIAutoModerationAction,
82+
_transformAPIMessageInteractionMetadata,
83+
_transformGuildScheduledEventRecurrenceRule,
84+
};

packages/discord.js/test/random.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
'use strict';
44

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

@@ -14,6 +14,7 @@ const client = new Client({
1414
GatewayIntentBits.GuildMessages,
1515
GatewayIntentBits.GuildMessageReactions,
1616
GatewayIntentBits.GuildMembers,
17+
GatewayIntentBits.MessageContent,
1718
],
1819
});
1920

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

189-
if (msg.content.startsWith('#eval') && msg.author.id === '66564597481480192') {
190+
if (msg.content.startsWith('#eval') && msg.author.id === owner) {
190191
try {
191192
const com = eval(msg.content.split(' ').slice(1).join(' '));
192193
msg.channel.send(`\`\`\`\n${com}\`\`\``);

packages/discord.js/typings/index.d.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ import {
186186
ReactionType,
187187
APIAuthorizingIntegrationOwnersMap,
188188
MessageReferenceType,
189+
GuildScheduledEventRecurrenceRuleWeekday,
190+
GuildScheduledEventRecurrenceRuleMonth,
191+
GuildScheduledEventRecurrenceRuleFrequency,
189192
} from 'discord-api-types/v10';
190193
import { ChildProcess } from 'node:child_process';
191194
import { EventEmitter } from 'node:events';
@@ -1779,6 +1782,7 @@ export class GuildScheduledEvent<Status extends GuildScheduledEventStatus = Guil
17791782
public entityMetadata: GuildScheduledEventEntityMetadata | null;
17801783
public userCount: number | null;
17811784
public creator: User | null;
1785+
public recurrenceRule: GuildScheduledEventRecurrenceRule | null;
17821786
public get createdTimestamp(): number;
17831787
public get createdAt(): Date;
17841788
public get scheduledStartAt(): Date | null;
@@ -1817,6 +1821,26 @@ export class GuildScheduledEvent<Status extends GuildScheduledEventStatus = Guil
18171821
public isScheduled(): this is GuildScheduledEvent<GuildScheduledEventStatus.Scheduled>;
18181822
}
18191823

1824+
export interface GuildScheduledEventRecurrenceRule {
1825+
startTimestamp: number;
1826+
get startAt(): Date;
1827+
endTimestamp: number | null;
1828+
get endAt(): Date | null;
1829+
frequency: GuildScheduledEventRecurrenceRuleFrequency;
1830+
interval: number;
1831+
byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[] | null;
1832+
byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[] | null;
1833+
byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[] | null;
1834+
byMonthDay: readonly number[] | null;
1835+
byYearDay: readonly number[] | null;
1836+
count: number | null;
1837+
}
1838+
1839+
export interface GuildScheduledEventRecurrenceRuleNWeekday {
1840+
n: number;
1841+
day: GuildScheduledEventRecurrenceRuleWeekday;
1842+
}
1843+
18201844
export class GuildTemplate extends Base {
18211845
private constructor(client: Client<true>, data: RawGuildTemplateData);
18221846
public createdTimestamp: number;
@@ -6167,14 +6191,29 @@ export interface GuildScheduledEventCreateOptions {
61676191
entityMetadata?: GuildScheduledEventEntityMetadataOptions;
61686192
image?: BufferResolvable | Base64Resolvable | null;
61696193
reason?: string;
6194+
recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions;
6195+
}
6196+
6197+
export interface GuildScheduledEventRecurrenceRuleOptions {
6198+
startAt: DateResolvable;
6199+
endAt: DateResolvable;
6200+
frequency: GuildScheduledEventRecurrenceRuleFrequency;
6201+
interval: number;
6202+
byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[];
6203+
byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[];
6204+
byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[];
6205+
byMonthDay: readonly number[];
6206+
byYearDay: readonly number[];
6207+
count: number;
61706208
}
61716209

61726210
export interface GuildScheduledEventEditOptions<
61736211
Status extends GuildScheduledEventStatus,
61746212
AcceptableStatus extends GuildScheduledEventSetStatusArg<Status>,
6175-
> extends Omit<Partial<GuildScheduledEventCreateOptions>, 'channel'> {
6213+
> extends Omit<Partial<GuildScheduledEventCreateOptions>, 'channel' | 'recurrenceRule'> {
61766214
channel?: GuildVoiceChannelResolvable | null;
61776215
status?: AcceptableStatus;
6216+
recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions | null;
61786217
}
61796218

61806219
export interface GuildScheduledEventEntityMetadata {

packages/discord.js/typings/index.test-d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ import {
209209
ApplicationEmoji,
210210
ApplicationEmojiManager,
211211
StickerPack,
212+
GuildScheduledEventManager,
212213
SendableChannels,
213214
PollData,
214215
} from '.';
@@ -2618,3 +2619,6 @@ client.on('interactionCreate', interaction => {
26182619
interaction.channel.send({ embeds: [] });
26192620
}
26202621
});
2622+
2623+
declare const guildScheduledEventManager: GuildScheduledEventManager;
2624+
await guildScheduledEventManager.edit(snowflake, { recurrenceRule: null });

0 commit comments

Comments
 (0)