@helyx/bot 0.4.31 → 0.4.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/events.js CHANGED
@@ -29,10 +29,26 @@ export function installModuleEvents(client, modules, logger, services) {
29
29
  moduleId: module.manifest.id,
30
30
  contribution,
31
31
  })));
32
+ const reactionRemoveAllContributions = modules.flatMap((module) => (module.events?.messageReactionRemoveAll ?? []).map((contribution) => ({
33
+ moduleId: module.manifest.id,
34
+ contribution,
35
+ })));
36
+ const reactionRemoveEmojiContributions = modules.flatMap((module) => (module.events?.messageReactionRemoveEmoji ?? []).map((contribution) => ({
37
+ moduleId: module.manifest.id,
38
+ contribution,
39
+ })));
40
+ const messageUpdateContributions = modules.flatMap((module) => (module.events?.guildMessageUpdate ?? []).map((contribution) => ({
41
+ moduleId: module.manifest.id,
42
+ contribution,
43
+ })));
32
44
  const messageDeleteContributions = modules.flatMap((module) => (module.events?.messageDelete ?? []).map((contribution) => ({
33
45
  moduleId: module.manifest.id,
34
46
  contribution,
35
47
  })));
48
+ const messageDeleteBulkContributions = modules.flatMap((module) => (module.events?.guildMessageDeleteBulk ?? []).map((contribution) => ({
49
+ moduleId: module.manifest.id,
50
+ contribution,
51
+ })));
36
52
  const memberAddContributions = modules.flatMap((module) => (module.events?.guildMemberAdd ?? []).map((contribution) => ({
37
53
  moduleId: module.manifest.id,
38
54
  contribution,
@@ -70,7 +86,11 @@ export function installModuleEvents(client, modules, logger, services) {
70
86
  guildVoiceSessionContributions.length === 0 &&
71
87
  reactionContributions.length === 0 &&
72
88
  reactionRemoveContributions.length === 0 &&
89
+ reactionRemoveAllContributions.length === 0 &&
90
+ reactionRemoveEmojiContributions.length === 0 &&
91
+ messageUpdateContributions.length === 0 &&
73
92
  messageDeleteContributions.length === 0 &&
93
+ messageDeleteBulkContributions.length === 0 &&
74
94
  memberAddContributions.length === 0 &&
75
95
  memberRemoveContributions.length === 0 &&
76
96
  guildRoleDeleteContributions.length === 0 &&
@@ -101,6 +121,11 @@ export function installModuleEvents(client, modules, logger, services) {
101
121
  (await item.contribution.isEligible({
102
122
  serverId: message.guildId,
103
123
  channelId: message.channelId,
124
+ userId: message.author.id,
125
+ userIsBot: message.author.bot,
126
+ userIsSystem: message.author.system || message.system,
127
+ webhookId: message.webhookId,
128
+ content: message.content,
104
129
  services,
105
130
  }))) {
106
131
  eligible.push(item);
@@ -156,12 +181,40 @@ export function installModuleEvents(client, modules, logger, services) {
156
181
  queue.enqueue(guildId, () => handleReactionRemove(reaction, user, details.burst));
157
182
  });
158
183
  }
184
+ if (reactionRemoveAllContributions.length > 0) {
185
+ client.on(Events.MessageReactionRemoveAll, (message) => {
186
+ if (message.guildId)
187
+ queue.enqueue(message.guildId, () => handleReactionRemoveAll(message));
188
+ });
189
+ }
190
+ if (reactionRemoveEmojiContributions.length > 0) {
191
+ client.on(Events.MessageReactionRemoveEmoji, (reaction) => {
192
+ const guildId = reaction.message.guildId;
193
+ if (guildId)
194
+ queue.enqueue(guildId, () => handleReactionRemoveEmoji(reaction));
195
+ });
196
+ }
197
+ if (messageUpdateContributions.length > 0) {
198
+ client.on(Events.MessageUpdate, (_before, message) => {
199
+ if (message.guildId)
200
+ queue.enqueue(message.guildId, () => handleMessageUpdate(message));
201
+ });
202
+ }
159
203
  if (messageDeleteContributions.length > 0) {
160
204
  client.on(Events.MessageDelete, (message) => {
161
205
  if (message.guildId)
162
206
  queue.enqueue(message.guildId, () => handleMessageDelete(message));
163
207
  });
164
208
  }
209
+ if (messageDeleteBulkContributions.length > 0) {
210
+ client.on(Events.MessageBulkDelete, (messages, channel) => {
211
+ if (channel.isDMBased())
212
+ return;
213
+ queue.enqueue(channel.guild.id, () => handleMessageDeleteBulk(channel.guild.id, channel.id, [
214
+ ...messages.keys(),
215
+ ]));
216
+ });
217
+ }
165
218
  if (memberAddContributions.length > 0) {
166
219
  client.on(Events.GuildMemberAdd, (member) => {
167
220
  queue.enqueue(member.guild.id, () => handleMemberAdd(member));
@@ -360,6 +413,7 @@ export function installModuleEvents(client, modules, logger, services) {
360
413
  const installations = services.get(HELYX_SERVICE_NAMES.installations);
361
414
  const member = await message.guild?.members.fetch(user.id);
362
415
  const userRoleIds = member ? [...member.roles.cache.keys()] : [];
416
+ const parentChannelId = messageParentChannelId(message);
363
417
  for (const { moduleId, contribution } of reactionContributions) {
364
418
  if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
365
419
  continue;
@@ -368,12 +422,21 @@ export function installModuleEvents(client, modules, logger, services) {
368
422
  await contribution.execute({
369
423
  serverId: message.guildId,
370
424
  channelId: message.channelId,
425
+ ...(parentChannelId ? { parentChannelId } : {}),
371
426
  messageId: message.id,
372
427
  userId: receivedUser.id,
373
428
  userRoleIds,
374
429
  emojiKey: reactionEmojiKey(reaction),
430
+ emojiIdentifier: reactionEmojiIdentifier(reaction),
431
+ reactionCount: Math.max(0, reaction.count ?? 0),
375
432
  reactionType: burst ? "burst" : "normal",
433
+ sourceAuthorId: message.author?.id ?? "",
434
+ sourceAuthorIsBot: message.author?.bot ?? false,
435
+ sourceAuthorIsWebhook: Boolean(message.webhookId),
436
+ sourceAuthorIsSystem: (message.author?.system ?? false) || (message.system ?? false),
437
+ sourceCreatedAt: message.createdAt ?? new Date(0),
376
438
  services,
439
+ fetchSourceMessage: () => sourceMessageSnapshot(message, messageClassificationAvailable),
377
440
  removeAddedReaction: () => reaction.users.remove(user.id).then(() => undefined),
378
441
  removeUserReaction: async (emojiIdentifier) => {
379
442
  const refreshed = await message.fetch();
@@ -465,10 +528,16 @@ export function installModuleEvents(client, modules, logger, services) {
465
528
  try {
466
529
  if (receivedUser.bot)
467
530
  return;
468
- const message = receivedReaction.message;
531
+ const reaction = receivedReaction.partial
532
+ ? await receivedReaction.fetch()
533
+ : receivedReaction;
534
+ const message = reaction.message.partial
535
+ ? await reaction.message.fetch()
536
+ : reaction.message;
469
537
  if (!message.guildId || !message.channelId)
470
538
  return;
471
539
  const installations = services.get(HELYX_SERVICE_NAMES.installations);
540
+ const parentChannelId = messageParentChannelId(message);
472
541
  for (const { moduleId, contribution } of reactionRemoveContributions) {
473
542
  if (!(await installations.isModuleEnabled(message.guildId, moduleId))) {
474
543
  continue;
@@ -477,9 +546,12 @@ export function installModuleEvents(client, modules, logger, services) {
477
546
  await contribution.execute({
478
547
  serverId: message.guildId,
479
548
  channelId: message.channelId,
549
+ ...(parentChannelId ? { parentChannelId } : {}),
480
550
  messageId: message.id,
481
551
  userId: receivedUser.id,
482
- emojiKey: reactionEmojiKey(receivedReaction),
552
+ emojiKey: reactionEmojiKey(reaction),
553
+ emojiIdentifier: reactionEmojiIdentifier(reaction),
554
+ reactionCount: Math.max(0, reaction.count ?? 0),
483
555
  reactionType: burst ? "burst" : "normal",
484
556
  services,
485
557
  });
@@ -502,6 +574,101 @@ export function installModuleEvents(client, modules, logger, services) {
502
574
  }, "Module reaction-remove event failed");
503
575
  }
504
576
  }
577
+ async function handleReactionRemoveAll(receivedMessage) {
578
+ const message = receivedMessage.partial
579
+ ? await receivedMessage.fetch().catch(() => receivedMessage)
580
+ : receivedMessage;
581
+ if (!message.guildId || !message.channelId)
582
+ return;
583
+ const installations = services.get(HELYX_SERVICE_NAMES.installations);
584
+ for (const { moduleId, contribution } of reactionRemoveAllContributions) {
585
+ try {
586
+ if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
587
+ continue;
588
+ await contribution.execute({
589
+ serverId: message.guildId,
590
+ channelId: message.channelId,
591
+ messageId: message.id,
592
+ services,
593
+ });
594
+ }
595
+ catch (error) {
596
+ logger.warn({
597
+ event: "module.reaction_remove_all_failed",
598
+ moduleId,
599
+ error: safeError(error),
600
+ messageId: message.id,
601
+ guildId: message.guildId,
602
+ }, "Module reaction-remove-all event failed");
603
+ }
604
+ }
605
+ }
606
+ async function handleReactionRemoveEmoji(receivedReaction) {
607
+ const reaction = receivedReaction.partial
608
+ ? await receivedReaction.fetch().catch(() => receivedReaction)
609
+ : receivedReaction;
610
+ const message = reaction.message;
611
+ if (!message.guildId || !message.channelId)
612
+ return;
613
+ const installations = services.get(HELYX_SERVICE_NAMES.installations);
614
+ for (const { moduleId, contribution } of reactionRemoveEmojiContributions) {
615
+ try {
616
+ if (!(await installations.isModuleEnabled(message.guildId, moduleId)))
617
+ continue;
618
+ await contribution.execute({
619
+ serverId: message.guildId,
620
+ channelId: message.channelId,
621
+ messageId: message.id,
622
+ emojiKey: reactionEmojiKey(reaction),
623
+ emojiIdentifier: reactionEmojiIdentifier(reaction),
624
+ services,
625
+ });
626
+ }
627
+ catch (error) {
628
+ logger.warn({
629
+ event: "module.reaction_remove_emoji_failed",
630
+ moduleId,
631
+ error: safeError(error),
632
+ messageId: message.id,
633
+ guildId: message.guildId,
634
+ }, "Module reaction-remove-emoji event failed");
635
+ }
636
+ }
637
+ }
638
+ async function handleMessageUpdate(receivedMessage) {
639
+ if (!receivedMessage.guildId || !receivedMessage.channelId)
640
+ return;
641
+ const installations = services.get(HELYX_SERVICE_NAMES.installations);
642
+ for (const { moduleId, contribution } of messageUpdateContributions) {
643
+ try {
644
+ if (!(await installations.isModuleEnabled(receivedMessage.guildId, moduleId)))
645
+ continue;
646
+ await contribution.execute({
647
+ serverId: receivedMessage.guildId,
648
+ channelId: receivedMessage.channelId,
649
+ messageId: receivedMessage.id,
650
+ services,
651
+ fetchSourceMessage: async () => {
652
+ const message = receivedMessage.partial
653
+ ? await receivedMessage.fetch().catch(() => null)
654
+ : receivedMessage;
655
+ return message
656
+ ? sourceMessageSnapshot(message, messageClassificationAvailable)
657
+ : null;
658
+ },
659
+ });
660
+ }
661
+ catch (error) {
662
+ logger.warn({
663
+ event: "module.message_update_failed",
664
+ moduleId,
665
+ error: safeError(error),
666
+ messageId: receivedMessage.id,
667
+ guildId: receivedMessage.guildId,
668
+ }, "Module message-update event failed");
669
+ }
670
+ }
671
+ }
505
672
  async function handleMessageDelete(message) {
506
673
  if (!message.guildId || !message.channelId)
507
674
  return;
@@ -529,6 +696,34 @@ export function installModuleEvents(client, modules, logger, services) {
529
696
  }
530
697
  }
531
698
  }
699
+ async function handleMessageDeleteBulk(guildId, channelId, messageIds) {
700
+ if (messageIds.length === 0)
701
+ return;
702
+ const installations = services.get(HELYX_SERVICE_NAMES.installations);
703
+ const boundedIds = messageIds.slice(0, 100);
704
+ for (const { moduleId, contribution } of messageDeleteBulkContributions) {
705
+ try {
706
+ if (!(await installations.isModuleEnabled(guildId, moduleId)))
707
+ continue;
708
+ await contribution.execute({
709
+ serverId: guildId,
710
+ channelId,
711
+ messageIds: boundedIds,
712
+ services,
713
+ });
714
+ }
715
+ catch (error) {
716
+ logger.warn({
717
+ event: "module.message_delete_bulk_failed",
718
+ moduleId,
719
+ error: safeError(error),
720
+ guildId,
721
+ channelId,
722
+ messageCount: boundedIds.length,
723
+ }, "Module message-delete-bulk event failed");
724
+ }
725
+ }
726
+ }
532
727
  async function handleVoiceState(before, after) {
533
728
  const memberId = after.id;
534
729
  const key = `${after.guild.id}:${memberId}`;
@@ -676,6 +871,78 @@ export function reactionEmojiKey(reaction) {
676
871
  ? `custom:${reaction.emoji.id}`
677
872
  : `unicode:${(reaction.emoji.name ?? "").normalize("NFKC")}`;
678
873
  }
874
+ export function reactionEmojiIdentifier(reaction) {
875
+ if (!reaction.emoji.id)
876
+ return reaction.emoji.name ?? "";
877
+ return `${reaction.emoji.name ?? "emoji"}:${reaction.emoji.id}`;
878
+ }
879
+ async function sourceMessageSnapshot(receivedMessage, contentAvailable) {
880
+ const message = receivedMessage.partial
881
+ ? await receivedMessage.fetch()
882
+ : receivedMessage;
883
+ const parentChannelId = message.channel.isThread()
884
+ ? (message.channel.parentId ?? undefined)
885
+ : undefined;
886
+ const authorDisplayName = message.member?.displayName ??
887
+ message.author.globalName ??
888
+ message.author.username;
889
+ const attachments = contentAvailable
890
+ ? [...message.attachments.values()].slice(0, 10).map((attachment) => ({
891
+ name: attachment.name.slice(0, 255),
892
+ url: attachment.url,
893
+ contentType: attachment.contentType,
894
+ size: attachment.size,
895
+ image: attachment.contentType === "image/png" ||
896
+ attachment.contentType === "image/jpeg" ||
897
+ attachment.contentType === "image/gif" ||
898
+ attachment.contentType === "image/webp",
899
+ spoiler: attachment.spoiler,
900
+ }))
901
+ : [];
902
+ return {
903
+ serverId: message.guildId,
904
+ channelId: message.channelId,
905
+ ...(message.channel.isThread()
906
+ ? message.channel.parent?.name
907
+ ? { channelName: message.channel.parent.name }
908
+ : {}
909
+ : "name" in message.channel && typeof message.channel.name === "string"
910
+ ? { channelName: message.channel.name }
911
+ : {}),
912
+ ...(parentChannelId ? { parentChannelId } : {}),
913
+ messageId: message.id,
914
+ jumpUrl: message.url,
915
+ authorId: message.author.id,
916
+ authorDisplayName,
917
+ authorAvatarUrl: message.author.displayAvatarURL({ size: 128 }),
918
+ authorIsBot: message.author.bot,
919
+ authorIsWebhook: Boolean(message.webhookId),
920
+ authorIsSystem: message.author.system || message.system,
921
+ createdAt: message.createdAt,
922
+ editedAt: message.editedAt,
923
+ ageRestricted: messageChannelAgeRestricted(message),
924
+ contentAvailable,
925
+ content: contentAvailable ? message.content.slice(0, 4_000) : "",
926
+ attachments,
927
+ reactions: [...message.reactions.cache.values()]
928
+ .slice(0, 20)
929
+ .map((reaction) => ({
930
+ emojiKey: reactionEmojiKey(reaction),
931
+ emojiIdentifier: reactionEmojiIdentifier(reaction),
932
+ count: Math.max(0, reaction.count),
933
+ })),
934
+ };
935
+ }
936
+ function messageChannelAgeRestricted(message) {
937
+ const channel = message.channel;
938
+ if (channel.isThread())
939
+ return channel.parent?.nsfw ?? false;
940
+ return "nsfw" in channel && channel.nsfw === true;
941
+ }
942
+ function messageParentChannelId(message) {
943
+ const channel = message.channel;
944
+ return channel?.isThread?.() ? (channel.parentId ?? undefined) : undefined;
945
+ }
679
946
  export function reactionCacheKey(emojiIdentifier) {
680
947
  const custom = /^(?:[A-Za-z0-9_]{2,32}):(\d{17,20})$/u.exec(emojiIdentifier);
681
948
  return custom?.[1] ?? emojiIdentifier;
@@ -767,6 +1034,34 @@ export class DiscordMessageDeliveryService {
767
1034
  };
768
1035
  }
769
1036
  }
1037
+ async fetchSource(input) {
1038
+ validateSnowflake(input.guildId, "guild");
1039
+ validateSnowflake(input.channelId, "channel");
1040
+ validateSnowflake(input.messageId, "message");
1041
+ const guild = await this.client.guilds.fetch(input.guildId);
1042
+ if (guild.id !== input.guildId)
1043
+ throw new Error("Discord guild validation failed");
1044
+ const channel = await guild.channels.fetch(input.channelId);
1045
+ if (!channel ||
1046
+ channel.guild.id !== guild.id ||
1047
+ !channel.isTextBased() ||
1048
+ channel.isDMBased() ||
1049
+ !channel.viewable)
1050
+ throw new Error("Discord source-message channel is unavailable");
1051
+ try {
1052
+ const message = await channel.messages.fetch(input.messageId);
1053
+ if (message.guildId !== input.guildId ||
1054
+ message.channelId !== input.channelId ||
1055
+ message.id !== input.messageId)
1056
+ throw new Error("Discord source message validation failed");
1057
+ return sourceMessageSnapshot(message, this.client.options.intents.has(GatewayIntentBits.MessageContent));
1058
+ }
1059
+ catch (error) {
1060
+ if (isUnknownMessage(error))
1061
+ return null;
1062
+ throw error;
1063
+ }
1064
+ }
770
1065
  }
771
1066
  export class DiscordMemberEnforcementService {
772
1067
  client;
@@ -1162,6 +1457,43 @@ export class DiscordResourceInspection {
1162
1457
  };
1163
1458
  });
1164
1459
  }
1460
+ async listMembers(input) {
1461
+ validateSnowflake(input.guildId, "guild");
1462
+ if (input.after)
1463
+ validateSnowflake(input.after, "member cursor");
1464
+ if (!Number.isSafeInteger(input.limit) ||
1465
+ input.limit < 1 ||
1466
+ input.limit > 100)
1467
+ throw new Error("Discord member list limit is invalid");
1468
+ if (!this.client.options.intents.has(GatewayIntentBits.GuildMembers))
1469
+ throw new Error("Discord guild-members intent is unavailable");
1470
+ const guild = await this.client.guilds.fetch(input.guildId);
1471
+ if (guild.id !== input.guildId)
1472
+ throw new Error("Discord guild validation failed");
1473
+ const members = await guild.members.fetch({
1474
+ limit: input.limit,
1475
+ ...(input.after ? { after: input.after } : {}),
1476
+ });
1477
+ const ordered = [...members.values()].sort((left, right) => BigInt(left.id) < BigInt(right.id)
1478
+ ? -1
1479
+ : BigInt(left.id) > BigInt(right.id)
1480
+ ? 1
1481
+ : 0);
1482
+ const items = ordered.map((member) => {
1483
+ const avatarUrl = memberAvatarUrl(member);
1484
+ return {
1485
+ userId: member.id,
1486
+ displayName: member.displayName,
1487
+ ...(avatarUrl ? { avatarUrl } : {}),
1488
+ isBot: member.user.bot,
1489
+ };
1490
+ });
1491
+ const nextCursor = items.at(-1)?.userId;
1492
+ return {
1493
+ items,
1494
+ ...(items.length === input.limit && nextCursor ? { nextCursor } : {}),
1495
+ };
1496
+ }
1165
1497
  async #resolveMemberGuild(input) {
1166
1498
  validateSnowflake(input.guildId, "guild");
1167
1499
  if (input.userIds.length > 50 ||
@@ -1295,6 +1627,7 @@ export class DiscordResourceInspection {
1295
1627
  addReactions: false,
1296
1628
  readMessageHistory: false,
1297
1629
  manageMessages: false,
1630
+ ageRestricted: false,
1298
1631
  };
1299
1632
  const guildMatches = channel.guild.id === guild.id;
1300
1633
  const me = guild.members.me ?? (await guild.members.fetchMe());
@@ -1322,6 +1655,9 @@ export class DiscordResourceInspection {
1322
1655
  (effective?.has(PermissionFlagsBits.ReadMessageHistory) ?? false),
1323
1656
  manageMessages: guildMatches &&
1324
1657
  (effective?.has(PermissionFlagsBits.ManageMessages) ?? false),
1658
+ ageRestricted: "nsfw" in channel && typeof channel.nsfw === "boolean"
1659
+ ? channel.nsfw
1660
+ : false,
1325
1661
  };
1326
1662
  }
1327
1663
  async inspectMessage(input) {
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@ export interface BotHandle {
9
9
  export declare function startBot(processEnvironment?: NodeJS.ProcessEnv): Promise<BotHandle>;
10
10
  export declare function resolveBotWorkerEntryPoint(entryUrl?: string): string;
11
11
  export declare function readBotVersion(): string;
12
- export declare function gatewayIntentsForModules(modules: readonly ModuleDefinition[], messageContentEnabled?: boolean): GatewayIntentBits[];
12
+ export declare function gatewayIntentsForModules(modules: readonly ModuleDefinition[], messageContentEnabled?: boolean, guildMembersEnabled?: boolean): GatewayIntentBits[];
13
13
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -27,7 +27,10 @@ import { parseShardWorkerLaunchContract, startShardWorker, } from "./shard-worke
27
27
  import { createDeveloperOperationsModule } from "./developer-operations-module.js";
28
28
  import { shardHealthSnapshotSchema } from "./shard-contracts.js";
29
29
  import { createSupportModule } from "./support-module.js";
30
+ import { createOAuthModule } from "./oauth-module.js";
31
+ import { HttpOAuthService } from "./oauth-service.js";
30
32
  import { DiscordOwnedMessageDeletionService } from "./discord-owned-message-service.js";
33
+ import { DiscordGuildHealthInspection } from "./discord-guild-health.js";
31
34
  import { CoreResourceAuthorizationService } from "./resource-authorization.js";
32
35
  import { createLocalScheduledTaskRoute, createScheduledTaskRunner, schedulerOperationalHealth, } from "./scheduled-task-runner.js";
33
36
  import { createLocalNumericWriteBehind } from "./numeric-write-behind.js";
@@ -129,7 +132,7 @@ async function startUnshardedBot(processEnvironment) {
129
132
  }, "Module databases ready");
130
133
  const featureModules = modulePackages.map(({ definition }) => definition);
131
134
  const client = new Client({
132
- intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED),
135
+ intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED, environment.HELYX_GUILD_MEMBERS_ENABLED),
133
136
  partials: [Partials.Message, Partials.Channel, Partials.Reaction],
134
137
  allowedMentions: { parse: [] },
135
138
  });
@@ -152,10 +155,14 @@ async function startUnshardedBot(processEnvironment) {
152
155
  })
153
156
  : undefined;
154
157
  const supportModule = createSupportModule(cloudConnector);
158
+ const oauthModule = environment.HELYX_OAUTH_ENABLED
159
+ ? createOAuthModule()
160
+ : undefined;
155
161
  const publicModules = [
156
162
  managementModule,
157
163
  permissionsModule,
158
164
  supportModule,
165
+ ...(oauthModule ? [oauthModule] : []),
159
166
  ...(cloudManagementModule ? [cloudManagementModule] : []),
160
167
  ...featureModules,
161
168
  ];
@@ -172,6 +179,7 @@ async function startUnshardedBot(processEnvironment) {
172
179
  managementModule,
173
180
  permissionsModule,
174
181
  supportModule,
182
+ ...(oauthModule ? [oauthModule] : []),
175
183
  ...(cloudManagementModule ? [cloudManagementModule] : []),
176
184
  ].map(({ manifest }) => manifest),
177
185
  modules: featureModules.map(({ manifest }) => manifest),
@@ -257,6 +265,9 @@ async function startUnshardedBot(processEnvironment) {
257
265
  }));
258
266
  platformServices.set(HELYX_SERVICE_NAMES.verificationMembers, new DiscordVerificationMemberService(client));
259
267
  }
268
+ if (environment.HELYX_OAUTH_ENABLED &&
269
+ environment.HELYX_OAUTH_INTERNAL_SECRET)
270
+ platformServices.set(HELYX_SERVICE_NAMES.oauth, new HttpOAuthService(environment.HELYX_CLOUD_API_URL, environment.HELYX_OAUTH_INTERNAL_SECRET));
260
271
  const numericWriteBehind = createLocalNumericWriteBehind(platformServices, publicModules);
261
272
  platformServices.set(HELYX_SERVICE_NAMES.numericWriteBehind, numericWriteBehind);
262
273
  platformServices.set(HELYX_SERVICE_NAMES.resourceAuthorization, new CoreResourceAuthorizationService(platformServices.get(HELYX_SERVICE_NAMES.permissions)));
@@ -265,6 +276,7 @@ async function startUnshardedBot(processEnvironment) {
265
276
  platformServices.set(HELYX_SERVICE_NAMES.memberEnforcement, new DiscordMemberEnforcementService(client));
266
277
  platformServices.set(HELYX_SERVICE_NAMES.memberRoles, new DiscordMemberRoleService(client));
267
278
  platformServices.set(HELYX_SERVICE_NAMES.discordResources, new DiscordResourceInspection(client));
279
+ platformServices.set(HELYX_SERVICE_NAMES.discordGuildHealth, new DiscordGuildHealthInspection(client, publicModules, serviceAccess));
268
280
  if (featureModules.some(({ manifest }) => manifest.id === "helyx.logging")) {
269
281
  platformServices.set(HELYX_SERVICE_NAMES.moduleLogging, new DiscordModuleLoggingService(client, database, publicModules, bootstrapLogger));
270
282
  }
@@ -277,6 +289,7 @@ async function startUnshardedBot(processEnvironment) {
277
289
  managementModule.manifest.id,
278
290
  permissionsModule.manifest.id,
279
291
  supportModule.manifest.id,
292
+ ...(oauthModule ? [oauthModule.manifest.id] : []),
280
293
  ...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
281
294
  ...(operationsModule ? [operationsModule.manifest.id] : []),
282
295
  ]);
@@ -288,6 +301,7 @@ async function startUnshardedBot(processEnvironment) {
288
301
  prepare: prepareGuilds,
289
302
  selfAuthorizingGuildModuleIds: new Set([
290
303
  supportModule.manifest.id,
304
+ ...(oauthModule ? [oauthModule.manifest.id] : []),
291
305
  ...(operationsModule ? [operationsModule.manifest.id] : []),
292
306
  ]),
293
307
  }, bootstrapLogger, serviceAccess);
@@ -469,10 +483,12 @@ function deploymentReference(processEnvironment) {
469
483
  processEnvironment.GIT_COMMIT_SHA;
470
484
  return value?.trim().slice(0, 12) || undefined;
471
485
  }
472
- export function gatewayIntentsForModules(modules, messageContentEnabled = false) {
486
+ export function gatewayIntentsForModules(modules, messageContentEnabled = false, guildMembersEnabled = false) {
473
487
  const intents = [GatewayIntentBits.Guilds];
474
488
  if (modules.some((module) => module.events?.messageReactionAdd?.length ||
475
- module.events?.messageReactionRemove?.length)) {
489
+ module.events?.messageReactionRemove?.length ||
490
+ module.events?.messageReactionRemoveAll?.length ||
491
+ module.events?.messageReactionRemoveEmoji?.length)) {
476
492
  intents.push(GatewayIntentBits.GuildMessageReactions);
477
493
  }
478
494
  const hasPrivilegedMessageCreate = modules.some((module) => module.events?.privilegedMessageCreate?.length);
@@ -480,16 +496,19 @@ export function gatewayIntentsForModules(modules, messageContentEnabled = false)
480
496
  if (((hasPrivilegedMessageCreate || requiresMessageContent) &&
481
497
  messageContentEnabled) ||
482
498
  modules.some((module) => module.events?.guildMessageCreate?.length ||
483
- module.events?.messageDelete?.length)) {
499
+ module.events?.guildMessageUpdate?.length ||
500
+ module.events?.messageDelete?.length ||
501
+ module.events?.guildMessageDeleteBulk?.length)) {
484
502
  intents.push(GatewayIntentBits.GuildMessages);
485
503
  }
486
504
  if ((hasPrivilegedMessageCreate || requiresMessageContent) &&
487
505
  messageContentEnabled) {
488
506
  intents.push(GatewayIntentBits.MessageContent);
489
507
  }
490
- if (modules.some((module) => module.requiredDiscordCapabilities?.includes("guild-members") ||
491
- module.events?.guildMemberAdd?.length ||
492
- module.events?.guildMemberRemove?.length)) {
508
+ if (guildMembersEnabled ||
509
+ modules.some((module) => module.requiredDiscordCapabilities?.includes("guild-members") ||
510
+ module.events?.guildMemberAdd?.length ||
511
+ module.events?.guildMemberRemove?.length)) {
493
512
  intents.push(GatewayIntentBits.GuildMembers);
494
513
  }
495
514
  if (modules.some((module) => module.events?.guildVoiceSession?.length)) {
@@ -6,6 +6,7 @@ const buttonStyles = {
6
6
  secondary: ButtonStyle.Secondary,
7
7
  success: ButtonStyle.Success,
8
8
  danger: ButtonStyle.Danger,
9
+ link: ButtonStyle.Link,
9
10
  };
10
11
  export function installInteractions(client, modules, registration, logger, services) {
11
12
  const commands = uniqueMap(modules.flatMap((module) => (module.interactions?.commands ?? []).map((contribution) => ({
@@ -702,9 +703,14 @@ export function toReplyOptions(response) {
702
703
  const allowedMentions = toAllowedUserMentions(response.allowedUserMentionIds);
703
704
  const selectRows = toSelectRows(response);
704
705
  if (response.componentsV2) {
705
- const flags = response.ephemeral
706
- ? MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral
707
- : MessageFlags.IsComponentsV2;
706
+ if (response.content !== undefined) {
707
+ throw new Error("Components V2 responses cannot include plain content");
708
+ }
709
+ let flags = Number(MessageFlags.IsComponentsV2);
710
+ if (response.ephemeral)
711
+ flags |= MessageFlags.Ephemeral;
712
+ if (response.suppressEmbeds)
713
+ flags |= MessageFlags.SuppressEmbeds;
708
714
  return {
709
715
  flags,
710
716
  ...(allowedMentions ? { allowedMentions } : {}),
@@ -728,9 +734,11 @@ export function toReplyOptions(response) {
728
734
  type: 1,
729
735
  components: response.buttons.map((button) => ({
730
736
  type: 2,
731
- custom_id: button.customId,
732
737
  label: button.label,
733
738
  style: buttonStyles[button.style],
739
+ ...(button.style === "link"
740
+ ? { url: button.url }
741
+ : { custom_id: button.customId }),
734
742
  ...(button.emojiIdentifier
735
743
  ? { emoji: toButtonEmoji(button.emojiIdentifier) }
736
744
  : {}),
@@ -750,13 +758,29 @@ export function toReplyOptions(response) {
750
758
  : undefined;
751
759
  if (embed && response.embed?.footer)
752
760
  embed.setFooter({ text: response.embed.footer });
761
+ if (embed && response.embed?.url)
762
+ embed.setURL(response.embed.url);
763
+ if (embed && response.embed?.author)
764
+ embed.setAuthor({
765
+ name: response.embed.author.name,
766
+ ...(response.embed.author.iconUrl
767
+ ? { iconURL: response.embed.author.iconUrl }
768
+ : {}),
769
+ });
770
+ if (embed && response.embed?.timestamp)
771
+ embed.setTimestamp(new Date(response.embed.timestamp));
772
+ if (embed && response.embed?.imageUrl)
773
+ embed.setImage(response.embed.imageUrl);
753
774
  const components = response.buttons?.length
754
775
  ? [
755
776
  new ActionRowBuilder().addComponents(response.buttons.map((button) => {
756
777
  const builder = new ButtonBuilder()
757
- .setCustomId(button.customId)
758
778
  .setLabel(button.label)
759
779
  .setStyle(buttonStyles[button.style]);
780
+ if (button.style === "link")
781
+ builder.setURL(button.url);
782
+ else
783
+ builder.setCustomId(button.customId);
760
784
  if (button.emojiIdentifier) {
761
785
  builder.setEmoji(toButtonEmoji(button.emojiIdentifier));
762
786
  }
@@ -769,8 +793,14 @@ export function toReplyOptions(response) {
769
793
  : [];
770
794
  components.push(...selectRows);
771
795
  return {
772
- ...(response.ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
796
+ ...(response.ephemeral || response.suppressEmbeds
797
+ ? {
798
+ flags: (response.ephemeral ? MessageFlags.Ephemeral : 0) |
799
+ (response.suppressEmbeds ? MessageFlags.SuppressEmbeds : 0),
800
+ }
801
+ : {}),
773
802
  ...(allowedMentions ? { allowedMentions } : {}),
803
+ ...(response.content === undefined ? {} : { content: response.content }),
774
804
  ...(embed ? { embeds: [embed] } : {}),
775
805
  components,
776
806
  };