seyfert 4.4.0 → 5.0.1-dev-27861043347.0

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.
Files changed (165) hide show
  1. package/lib/api/Routes/cdn.d.ts +1 -1
  2. package/lib/api/Routes/guilds.d.ts +2 -2
  3. package/lib/api/api.d.ts +16 -4
  4. package/lib/api/api.js +153 -30
  5. package/lib/api/shared.d.ts +30 -1
  6. package/lib/api/utils/constants.d.ts +1 -1
  7. package/lib/api/utils/constants.js +1 -1
  8. package/lib/api/utils/utils.d.ts +2 -1
  9. package/lib/api/utils/utils.js +1 -1
  10. package/lib/builders/MediaGallery.d.ts +1 -1
  11. package/lib/builders/MediaGallery.js +10 -0
  12. package/lib/builders/Modal.d.ts +18 -8
  13. package/lib/builders/Modal.js +36 -7
  14. package/lib/builders/Poll.js +18 -0
  15. package/lib/builders/RadioGroup.d.ts +4 -3
  16. package/lib/builders/RadioGroup.js +24 -3
  17. package/lib/builders/SelectMenu.d.ts +8 -6
  18. package/lib/builders/SelectMenu.js +24 -3
  19. package/lib/builders/types.d.ts +3 -2
  20. package/lib/cache/adapters/limited.js +1 -1
  21. package/lib/cache/index.d.ts +16 -22
  22. package/lib/cache/index.js +38 -2
  23. package/lib/cache/resources/bans.d.ts +1 -1
  24. package/lib/cache/resources/channels.d.ts +1 -1
  25. package/lib/cache/resources/default/base.js +1 -1
  26. package/lib/cache/resources/default/guild-based.d.ts +4 -4
  27. package/lib/cache/resources/default/guild-based.js +1 -1
  28. package/lib/cache/resources/default/guild-related.d.ts +4 -4
  29. package/lib/cache/resources/default/guild-related.js +1 -1
  30. package/lib/cache/resources/emojis.d.ts +1 -1
  31. package/lib/cache/resources/members.d.ts +1 -1
  32. package/lib/cache/resources/messages.d.ts +2 -2
  33. package/lib/cache/resources/overwrites.d.ts +1 -1
  34. package/lib/cache/resources/roles.d.ts +1 -1
  35. package/lib/cache/resources/stickers.d.ts +1 -1
  36. package/lib/cache/resources/voice-states.d.ts +1 -1
  37. package/lib/client/base.d.ts +77 -21
  38. package/lib/client/base.js +583 -34
  39. package/lib/client/client.d.ts +26 -3
  40. package/lib/client/client.js +33 -9
  41. package/lib/client/collectors.d.ts +9 -8
  42. package/lib/client/collectors.js +16 -43
  43. package/lib/client/httpclient.d.ts +3 -0
  44. package/lib/client/index.d.ts +3 -1
  45. package/lib/client/index.js +4 -0
  46. package/lib/client/intents.d.ts +3 -0
  47. package/lib/client/intents.js +9 -0
  48. package/lib/client/plugins/api.d.ts +4 -0
  49. package/lib/client/plugins/api.js +550 -0
  50. package/lib/client/plugins/errors.d.ts +25 -0
  51. package/lib/client/plugins/errors.js +79 -0
  52. package/lib/client/plugins/order.d.ts +10 -0
  53. package/lib/client/plugins/order.js +32 -0
  54. package/lib/client/plugins/registry.d.ts +273 -0
  55. package/lib/client/plugins/registry.js +868 -0
  56. package/lib/client/plugins/shared.d.ts +23 -0
  57. package/lib/client/plugins/shared.js +193 -0
  58. package/lib/client/plugins/types.d.ts +402 -0
  59. package/lib/client/plugins/types.js +8 -0
  60. package/lib/client/plugins.d.ts +78 -0
  61. package/lib/client/plugins.js +558 -0
  62. package/lib/client/workerclient.d.ts +10 -1
  63. package/lib/client/workerclient.js +21 -13
  64. package/lib/collection.d.ts +10 -8
  65. package/lib/collection.js +19 -25
  66. package/lib/commands/applications/chat.d.ts +18 -16
  67. package/lib/commands/applications/chat.js +56 -6
  68. package/lib/commands/applications/chatcontext.d.ts +23 -13
  69. package/lib/commands/applications/chatcontext.js +23 -2
  70. package/lib/commands/applications/entryPoint.d.ts +4 -3
  71. package/lib/commands/applications/entryPoint.js +1 -1
  72. package/lib/commands/applications/entrycontext.d.ts +7 -7
  73. package/lib/commands/applications/entrycontext.js +3 -1
  74. package/lib/commands/applications/menu.d.ts +4 -3
  75. package/lib/commands/applications/menucontext.d.ts +7 -7
  76. package/lib/commands/applications/menucontext.js +3 -1
  77. package/lib/commands/applications/options.d.ts +12 -12
  78. package/lib/commands/applications/shared.d.ts +31 -12
  79. package/lib/commands/decorators.d.ts +48 -36
  80. package/lib/commands/decorators.js +13 -3
  81. package/lib/commands/handle.d.ts +15 -4
  82. package/lib/commands/handle.js +224 -142
  83. package/lib/commands/handler.d.ts +21 -7
  84. package/lib/commands/handler.js +149 -62
  85. package/lib/commands/index.d.ts +1 -0
  86. package/lib/commands/optionresolver.d.ts +2 -2
  87. package/lib/common/it/error.d.ts +8 -0
  88. package/lib/common/it/error.js +7 -0
  89. package/lib/common/it/formatter.d.ts +14 -11
  90. package/lib/common/it/formatter.js +4 -3
  91. package/lib/common/it/logger.d.ts +6 -1
  92. package/lib/common/it/logger.js +17 -3
  93. package/lib/common/it/utils.d.ts +4 -4
  94. package/lib/common/it/utils.js +11 -5
  95. package/lib/common/shorters/application.d.ts +2 -2
  96. package/lib/common/shorters/bans.d.ts +7 -3
  97. package/lib/common/shorters/bans.js +12 -4
  98. package/lib/common/shorters/channels.d.ts +1 -1
  99. package/lib/common/shorters/channels.js +1 -1
  100. package/lib/common/shorters/interaction.d.ts +1 -1
  101. package/lib/common/shorters/members.d.ts +6 -6
  102. package/lib/common/shorters/members.js +7 -7
  103. package/lib/common/shorters/messages.d.ts +1 -1
  104. package/lib/common/shorters/users.d.ts +1 -1
  105. package/lib/common/shorters/webhook.d.ts +3 -2
  106. package/lib/common/shorters/webhook.js +0 -7
  107. package/lib/common/types/options.d.ts +4 -0
  108. package/lib/common/types/util.d.ts +8 -0
  109. package/lib/common/types/write.d.ts +2 -1
  110. package/lib/components/StringSelectMenuComponent.d.ts +1 -1
  111. package/lib/components/componentcommand.d.ts +8 -7
  112. package/lib/components/componentcontext.d.ts +21 -20
  113. package/lib/components/componentcontext.js +7 -2
  114. package/lib/components/handler.d.ts +18 -6
  115. package/lib/components/handler.js +102 -43
  116. package/lib/components/modalcommand.d.ts +5 -4
  117. package/lib/components/modalcontext.d.ts +35 -15
  118. package/lib/components/modalcontext.js +54 -1
  119. package/lib/events/event.d.ts +9 -3
  120. package/lib/events/handler.d.ts +51 -6
  121. package/lib/events/handler.js +163 -34
  122. package/lib/events/hooks/interactions.d.ts +1 -1
  123. package/lib/events/hooks/voice.d.ts +1 -2
  124. package/lib/events/hooks/voice.js +2 -1
  125. package/lib/events/utils.d.ts +4 -0
  126. package/lib/events/utils.js +59 -0
  127. package/lib/index.d.ts +9 -4
  128. package/lib/index.js +12 -7
  129. package/lib/langs/handler.d.ts +14 -9
  130. package/lib/langs/handler.js +45 -8
  131. package/lib/structures/Application.d.ts +1 -1
  132. package/lib/structures/Emoji.d.ts +2 -2
  133. package/lib/structures/Emoji.js +2 -2
  134. package/lib/structures/Guild.d.ts +3 -3
  135. package/lib/structures/GuildBan.d.ts +3 -3
  136. package/lib/structures/GuildBan.js +3 -3
  137. package/lib/structures/GuildMember.d.ts +8 -8
  138. package/lib/structures/GuildMember.js +28 -7
  139. package/lib/structures/GuildRole.d.ts +1 -1
  140. package/lib/structures/GuildRole.js +2 -2
  141. package/lib/structures/Interaction.d.ts +10 -8
  142. package/lib/structures/Interaction.js +28 -9
  143. package/lib/structures/Message.d.ts +10 -3
  144. package/lib/structures/Message.js +22 -10
  145. package/lib/structures/VoiceState.d.ts +4 -0
  146. package/lib/structures/VoiceState.js +12 -0
  147. package/lib/structures/Webhook.d.ts +9 -1
  148. package/lib/structures/Webhook.js +4 -1
  149. package/lib/structures/channels.d.ts +16 -9
  150. package/lib/structures/channels.js +22 -21
  151. package/lib/structures/extra/BitField.d.ts +2 -2
  152. package/lib/structures/extra/BitField.js +2 -1
  153. package/lib/structures/extra/DiscordBase.js +1 -1
  154. package/lib/structures/extra/Permissions.d.ts +2 -3
  155. package/lib/structures/extra/Permissions.js +6 -1
  156. package/lib/types/payloads/_interactions/responses.d.ts +2 -2
  157. package/lib/types/payloads/components.d.ts +4 -4
  158. package/lib/websocket/constants/index.js +2 -1
  159. package/lib/websocket/discord/shard.d.ts +2 -2
  160. package/lib/websocket/discord/sharder.d.ts +8 -7
  161. package/lib/websocket/discord/sharder.js +25 -5
  162. package/lib/websocket/discord/shared.d.ts +19 -6
  163. package/lib/websocket/discord/workermanager.d.ts +21 -7
  164. package/lib/websocket/discord/workermanager.js +28 -4
  165. package/package.json +5 -3
@@ -82,4 +82,4 @@ export interface CDNRoute {
82
82
  };
83
83
  };
84
84
  }
85
- export type UserAvatarDefault = 1 | 2 | 3 | 4 | 5 | number;
85
+ export type UserAvatarDefault = 0 | 1 | 2 | 3 | 4 | 5;
@@ -1,6 +1,6 @@
1
1
  import type { Identify, OmitInsert } from '../../common';
2
2
  import type { APIThreadChannel, RESTDeleteAPIAutoModerationRuleResult, RESTDeleteAPIGuildBanResult, RESTDeleteAPIGuildEmojiResult, RESTDeleteAPIGuildIntegrationResult, RESTDeleteAPIGuildMemberResult, RESTDeleteAPIGuildMemberRoleResult, RESTDeleteAPIGuildResult, RESTDeleteAPIGuildRoleResult, RESTDeleteAPIGuildScheduledEventResult, RESTDeleteAPIGuildStickerResult, RESTDeleteAPIGuildTemplateResult, RESTGetAPIAuditLogQuery, RESTGetAPIAuditLogResult, RESTGetAPIAutoModerationRuleResult, RESTGetAPIAutoModerationRulesResult, RESTGetAPIGuildBanResult, RESTGetAPIGuildBansQuery, RESTGetAPIGuildBansResult, RESTGetAPIGuildChannelsResult, RESTGetAPIGuildEmojiResult, RESTGetAPIGuildEmojisResult, RESTGetAPIGuildIntegrationsResult, RESTGetAPIGuildInvitesResult, RESTGetAPIGuildMemberResult, RESTGetAPIGuildMembersQuery, RESTGetAPIGuildMembersResult, RESTGetAPIGuildMembersSearchQuery, RESTGetAPIGuildMembersSearchResult, RESTGetAPIGuildMessagesSearchQuery, RESTGetAPIGuildMessagesSearchResult, RESTGetAPIGuildPreviewResult, RESTGetAPIGuildPruneCountQuery, RESTGetAPIGuildPruneCountResult, RESTGetAPIGuildQuery, RESTGetAPIGuildResult, RESTGetAPIGuildRoleMemberCountsResult, RESTGetAPIGuildRoleResult, RESTGetAPIGuildRolesResult, RESTGetAPIGuildScheduledEventQuery, RESTGetAPIGuildScheduledEventResult, RESTGetAPIGuildScheduledEventsQuery, RESTGetAPIGuildScheduledEventsResult, RESTGetAPIGuildScheduledEventUsersQuery, RESTGetAPIGuildScheduledEventUsersResult, RESTGetAPIGuildSoundboardSoundsResult, RESTGetAPIGuildStickerResult, RESTGetAPIGuildStickersResult, RESTGetAPIGuildTemplatesResult, RESTGetAPIGuildThreadsResult, RESTGetAPIGuildVanityUrlResult, RESTGetAPIGuildVoiceRegionsResult, RESTGetAPIGuildVoiceStateCurrentMemberResult, RESTGetAPIGuildVoiceStateUserResult, RESTGetAPIGuildWebhooksResult, RESTGetAPIGuildWelcomeScreenResult, RESTGetAPIGuildWidgetImageQuery, RESTGetAPIGuildWidgetImageResult, RESTGetAPIGuildWidgetJSONResult, RESTGetAPIGuildWidgetSettingsResult, RESTGetAPITemplateResult, RESTPatchAPIAutoModerationRuleJSONBody, RESTPatchAPIAutoModerationRuleResult, RESTPatchAPICurrentGuildMemberJSONBody, RESTPatchAPIGuildChannelPositionsJSONBody, RESTPatchAPIGuildChannelPositionsResult, RESTPatchAPIGuildEmojiJSONBody, RESTPatchAPIGuildEmojiResult, RESTPatchAPIGuildJSONBody, RESTPatchAPIGuildMemberJSONBody, RESTPatchAPIGuildMemberResult, RESTPatchAPIGuildResult, RESTPatchAPIGuildRoleJSONBody, RESTPatchAPIGuildRolePositionsJSONBody, RESTPatchAPIGuildRolePositionsResult, RESTPatchAPIGuildRoleResult, RESTPatchAPIGuildScheduledEventJSONBody, RESTPatchAPIGuildScheduledEventResult, RESTPatchAPIGuildSoundboardSoundJSONBody, RESTPatchAPIGuildSoundboardSoundResult, RESTPatchAPIGuildStickerJSONBody, RESTPatchAPIGuildStickerResult, RESTPatchAPIGuildTemplateJSONBody, RESTPatchAPIGuildTemplateResult, RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody, RESTPatchAPIGuildVoiceStateCurrentMemberResult, RESTPatchAPIGuildVoiceStateUserJSONBody, RESTPatchAPIGuildVoiceStateUserResult, RESTPatchAPIGuildWelcomeScreenJSONBody, RESTPatchAPIGuildWelcomeScreenResult, RESTPatchAPIGuildWidgetSettingsJSONBody, RESTPatchAPIGuildWidgetSettingsResult, RESTPostAPIAutoModerationRuleJSONBody, RESTPostAPIAutoModerationRuleResult, RESTPostAPIGuildBulkBanJSONBody, RESTPostAPIGuildBulkBanResult, RESTPostAPIGuildChannelJSONBody, RESTPostAPIGuildChannelResult, RESTPostAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiResult, RESTPostAPIGuildPruneJSONBody, RESTPostAPIGuildPruneResult, RESTPostAPIGuildRoleJSONBody, RESTPostAPIGuildRoleResult, RESTPostAPIGuildScheduledEventJSONBody, RESTPostAPIGuildScheduledEventResult, RESTPostAPIGuildSoundboardSoundJSONBody, RESTPostAPIGuildSoundboardSoundResult, RESTPostAPIGuildStickerFormDataBody, RESTPostAPIGuildStickerResult, RESTPostAPIGuildsMFAJSONBody, RESTPostAPIGuildsMFAResult, RESTPostAPIGuildTemplatesJSONBody, RESTPostAPIGuildTemplatesResult, RESTPostAPITemplateCreateGuildJSONBody, RESTPostAPITemplateCreateGuildResult, RESTPutAPIGuildBanJSONBody, RESTPutAPIGuildBanResult, RESTPutAPIGuildMemberJSONBody, RESTPutAPIGuildMemberResult, RESTPutAPIGuildMemberRoleResult, RESTPutAPIGuildTemplateSyncResult } from '../../types';
3
- import type { RestArguments, RestArgumentsNoBody } from '../api';
3
+ import type { RestArguments, RestArgumentsNoBody, RestArgumentsRequiredQuery } from '../api';
4
4
  import type { RawFile } from '../shared';
5
5
  export interface GuildRoutes {
6
6
  guilds: {
@@ -40,7 +40,7 @@ export interface GuildRoutes {
40
40
  members: {
41
41
  get(args?: RestArgumentsNoBody<RESTGetAPIGuildMembersQuery>): Promise<RESTGetAPIGuildMembersResult>;
42
42
  search: {
43
- get(args: RestArgumentsNoBody<RESTGetAPIGuildMembersSearchQuery>): Promise<RESTGetAPIGuildMembersSearchResult>;
43
+ get(args: RestArgumentsRequiredQuery<RESTGetAPIGuildMembersSearchQuery>): Promise<RESTGetAPIGuildMembersSearchResult>;
44
44
  };
45
45
  '@me': {
46
46
  patch(args: RestArguments<RESTPatchAPICurrentGuildMemberJSONBody>): Promise<RESTGetAPIGuildMemberResult>;
package/lib/api/api.d.ts CHANGED
@@ -3,14 +3,15 @@ import { type Awaitable, Logger, SeyfertError } from '../common';
3
3
  import type { WorkerSendApiRequest } from '../websocket/discord/worker';
4
4
  import { Bucket } from './bucket';
5
5
  import type { APIRoutes } from './Routes';
6
- import { type ApiHandlerInternalOptions, type ApiHandlerOptions, type ApiRequestOptions, type HttpMethods, type RawFile, type RequestHeaders } from './shared';
7
- export interface ApiHandler {
6
+ import { type ApiHandlerInternalOptions, type ApiHandlerOptions, type ApiRequestOptions, type HttpMethods, type RawFile, type RequestHeaders, type RestObserveOptions, type RestObserver, type RestObserverDisposer, type RestObserverEntry } from './shared';
7
+ export type { RestObserveOptions, RestObserver, RestObserverDisposer, RestObserverEntry, RestObserverFailPayload, RestObserverRatelimitPayload, RestObserverRequestPayload, RestObserverSuccessPayload, } from './shared';
8
+ export interface ApiHandler<TClient = unknown> {
8
9
  debugger?: Logger;
9
10
  }
10
11
  export type OnRatelimitCallback = (response: Response, request: ApiRequestOptions) => Awaitable<any>;
11
12
  export type OnSuccessRequestCallback = (method: HttpMethods, url: `/${string}`, response: Response) => Awaitable<any>;
12
13
  export type OnFailRequestCallback = (method: HttpMethods, url: `/${string}`, error: unknown, statusCode?: number) => Awaitable<any>;
13
- export declare class ApiHandler {
14
+ export declare class ApiHandler<TClient = unknown> {
14
15
  options: ApiHandlerInternalOptions;
15
16
  globalBlock: boolean;
16
17
  ratelimits: Map<string, Bucket>;
@@ -23,22 +24,30 @@ export declare class ApiHandler {
23
24
  onRatelimit?: OnRatelimitCallback;
24
25
  onSuccessRequest?: OnSuccessRequestCallback;
25
26
  onFailRequest?: OnFailRequestCallback;
27
+ pluginRestObserverProvider?: () => readonly RestObserverEntry[];
28
+ pluginRestObserverClient?: TClient;
29
+ private restObservers;
26
30
  constructor(options: ApiHandlerOptions);
31
+ observe(observer: RestObserver<TClient>, _opts?: RestObserveOptions): RestObserverDisposer;
27
32
  set debug(active: boolean);
28
33
  get proxy(): APIRoutes;
29
34
  globalUnblock(): void;
30
35
  randomUUID(): UUID;
31
36
  protected sendMessage(_body: WorkerSendApiRequest): void;
32
37
  protected postMessage<T = unknown>(body: WorkerSendApiRequest): Promise<T>;
38
+ private notifyRequest;
33
39
  private notifySuccessRequest;
34
40
  private notifyFailRequest;
41
+ private notifyRatelimit;
42
+ private notifyRestObservers;
43
+ private notifyRestObserverEntries;
35
44
  request<T = unknown>(method: HttpMethods, url: `/${string}`, { auth, ...request }?: ApiRequestOptions): Promise<T>;
36
45
  parseError(method: HttpMethods, route: `/${string}`, response: Response, result: string | Record<string, any>, originTrace?: {
37
46
  stack?: string;
38
47
  }): SeyfertError;
39
48
  parseValidationError(data: Record<string, any>, path?: string, errors?: string[]): string[];
40
49
  handle50X(method: HttpMethods, url: `/${string}`, request: ApiRequestOptions, next: () => void): Promise<unknown>;
41
- handle429(route: string, method: HttpMethods, url: `/${string}`, request: ApiRequestOptions, response: Response, result: string, next: () => void, reject: (err: unknown) => void, now: number): Promise<unknown>;
50
+ handle429(route: string, method: HttpMethods, url: `/${string}`, request: ApiRequestOptions, response: Response, result: string, next: () => void, reject: (err: unknown) => void, now: number, notifyUrl: `/${string}`): Promise<unknown>;
42
51
  clearResetInterval(route: string): void;
43
52
  setResetBucket(route: string, resp: Response, now: number, headerNow: number): void;
44
53
  setRatelimitsBucket(route: string, resp: Response): void;
@@ -63,3 +72,6 @@ export type RestArgumentsNoBody<Q extends never | Record<string, any> = never> =
63
72
  query?: Q;
64
73
  files?: RawFile[];
65
74
  } & RequestOptions;
75
+ export type RestArgumentsRequiredQuery<Q extends Record<string, any>> = Omit<RestArgumentsNoBody<Q>, 'query'> & {
76
+ query: Q;
77
+ };
package/lib/api/api.js CHANGED
@@ -18,6 +18,9 @@ class ApiHandler {
18
18
  onRatelimit;
19
19
  onSuccessRequest;
20
20
  onFailRequest;
21
+ pluginRestObserverProvider;
22
+ pluginRestObserverClient;
23
+ restObservers = new Map();
21
24
  constructor(options) {
22
25
  this.options = {
23
26
  baseUrl: 'api/v10',
@@ -59,6 +62,17 @@ class ApiHandler {
59
62
  };
60
63
  }
61
64
  }
65
+ observe(observer, _opts) {
66
+ const id = Symbol('rest-observer');
67
+ this.restObservers.set(id, { observer });
68
+ let disposed = false;
69
+ return () => {
70
+ if (disposed)
71
+ return;
72
+ disposed = true;
73
+ this.restObservers.delete(id);
74
+ };
75
+ }
62
76
  set debug(active) {
63
77
  this.debugger = active
64
78
  ? new common_1.Logger({
@@ -91,7 +105,22 @@ class ApiHandler {
91
105
  this.workerPromises.set(body.nonce, { reject: rej, resolve: res });
92
106
  });
93
107
  }
94
- async notifySuccessRequest(method, url, response) {
108
+ async notifyRequest(method, url, request) {
109
+ await this.notifyRestObservers('onRequest', () => freezeRestPayload({
110
+ client: this.pluginRestObserverClient,
111
+ method,
112
+ url,
113
+ request: readonlyRequestOptions(request),
114
+ }));
115
+ }
116
+ async notifySuccessRequest(method, url, response, request) {
117
+ await this.notifyRestObservers('onSuccess', () => freezeRestPayload({
118
+ client: this.pluginRestObserverClient,
119
+ method,
120
+ url,
121
+ request: readonlyRequestOptions(request),
122
+ response,
123
+ }));
95
124
  try {
96
125
  await this.onSuccessRequest?.(method, url, response);
97
126
  }
@@ -99,7 +128,15 @@ class ApiHandler {
99
128
  this.debugger?.warn('onSuccessRequest callback error', error);
100
129
  }
101
130
  }
102
- async notifyFailRequest(method, url, error, statusCode) {
131
+ async notifyFailRequest(method, url, error, statusCode, request) {
132
+ await this.notifyRestObservers('onFail', () => freezeRestPayload({
133
+ client: this.pluginRestObserverClient,
134
+ method,
135
+ url,
136
+ request: readonlyRequestOptions(request),
137
+ error,
138
+ statusCode,
139
+ }));
103
140
  try {
104
141
  await this.onFailRequest?.(method, url, error, statusCode);
105
142
  }
@@ -107,6 +144,49 @@ class ApiHandler {
107
144
  this.debugger?.warn('onFailRequest callback error', callbackError);
108
145
  }
109
146
  }
147
+ async notifyRatelimit(response, request, method, url) {
148
+ await this.notifyRestObservers('onRatelimit', () => freezeRestPayload({
149
+ client: this.pluginRestObserverClient,
150
+ method,
151
+ url,
152
+ request: readonlyRequestOptions(request),
153
+ response,
154
+ }));
155
+ try {
156
+ await this.onRatelimit?.(response, request);
157
+ }
158
+ catch (error) {
159
+ this.debugger?.warn('onRatelimit callback error', error);
160
+ }
161
+ }
162
+ async notifyRestObservers(name, createPayload) {
163
+ let payload;
164
+ let hasPayload = false;
165
+ const getPayload = () => {
166
+ if (!hasPayload) {
167
+ payload = createPayload();
168
+ hasPayload = true;
169
+ }
170
+ return payload;
171
+ };
172
+ await this.notifyRestObserverEntries(name, getPayload, this.pluginRestObserverProvider?.() ?? []);
173
+ await this.notifyRestObserverEntries(name, getPayload, this.restObservers.values());
174
+ }
175
+ async notifyRestObserverEntries(name, getPayload, entries) {
176
+ for (const entry of entries) {
177
+ const observer = entry.observer[name];
178
+ if (!observer)
179
+ continue;
180
+ const payload = getPayload();
181
+ try {
182
+ await observer(payload);
183
+ }
184
+ catch (error) {
185
+ const source = entry.plugin ? `[plugin:${entry.plugin}] ` : '';
186
+ this.debugger?.warn(`${source}REST observer "${name}" callback error`, error);
187
+ }
188
+ }
189
+ }
110
190
  async request(method, url, { auth = true, ...request } = {}) {
111
191
  const originTrace = {};
112
192
  Error.captureStackTrace(originTrace, this.request);
@@ -127,14 +207,24 @@ class ApiHandler {
127
207
  const headers = {
128
208
  'User-Agent': this.options.userAgent,
129
209
  };
130
- const { data, finalUrl } = this.parseRequest({
131
- url,
132
- headers,
133
- request: { ...request, auth },
134
- });
210
+ let data;
211
+ let finalUrl;
212
+ try {
213
+ ({ data, finalUrl } = this.parseRequest({
214
+ url,
215
+ headers,
216
+ request: { ...request, auth },
217
+ }));
218
+ }
219
+ catch (err) {
220
+ next();
221
+ reject(err);
222
+ return;
223
+ }
135
224
  let response;
136
225
  try {
137
226
  const requestUrl = `${this.options.domain}/${this.options.baseUrl}${finalUrl}`;
227
+ await this.notifyRequest(method, finalUrl, { ...request, auth });
138
228
  this.debugger?.debug(`Sending, Method: ${method} | Url: [${finalUrl}](${route}) | Auth: ${auth}`);
139
229
  response = await fetch(requestUrl, {
140
230
  method,
@@ -145,7 +235,7 @@ class ApiHandler {
145
235
  }
146
236
  catch (err) {
147
237
  this.debugger?.debug('Fetch error', err);
148
- await this.notifyFailRequest(method, finalUrl, err);
238
+ await this.notifyFailRequest(method, finalUrl, err, undefined, { ...request, auth });
149
239
  next();
150
240
  reject(err);
151
241
  return;
@@ -154,13 +244,14 @@ class ApiHandler {
154
244
  const headerNow = Date.parse(response.headers.get('date') ?? '');
155
245
  this.setRatelimitsBucket(route, response);
156
246
  this.setResetBucket(route, response, now, headerNow);
247
+ const observerResponse = response.clone();
157
248
  let result = await response.text();
158
249
  if (response.status >= 300) {
159
250
  if (response.status === 429) {
160
- const result429 = await this.handle429(route, method, url, request, response, result, next, reject, now);
251
+ const result429 = await this.handle429(route, method, url, { ...request, auth }, observerResponse, result, next, reject, now, finalUrl);
161
252
  if (result429 !== false)
162
253
  return resolve(result429);
163
- await this.notifyFailRequest(method, finalUrl, result, response.status);
254
+ await this.notifyFailRequest(method, finalUrl, result, response.status, { ...request, auth });
164
255
  return this.clearResetInterval(route);
165
256
  }
166
257
  if ([502, 503].includes(response.status) && ++attempts < 4) {
@@ -176,7 +267,7 @@ class ApiHandler {
176
267
  }
177
268
  catch (err) {
178
269
  this.debugger?.warn('SeyfertError parsing result error (', result, ')', err);
179
- await this.notifyFailRequest(method, finalUrl, err, response.status);
270
+ await this.notifyFailRequest(method, finalUrl, err, response.status, { ...request, auth });
180
271
  reject(err);
181
272
  return;
182
273
  }
@@ -184,7 +275,7 @@ class ApiHandler {
184
275
  }
185
276
  const parsedError = this.parseError(method, route, response, result, originTrace);
186
277
  this.debugger?.warn(parsedError.message);
187
- await this.notifyFailRequest(method, finalUrl, parsedError, response.status);
278
+ await this.notifyFailRequest(method, finalUrl, parsedError, response.status, { ...request, auth });
188
279
  reject(parsedError);
189
280
  return;
190
281
  }
@@ -195,14 +286,14 @@ class ApiHandler {
195
286
  }
196
287
  catch (err) {
197
288
  this.debugger?.warn('Failed parsing result (', result, ')', err);
198
- await this.notifyFailRequest(method, finalUrl, err, response.status);
289
+ await this.notifyFailRequest(method, finalUrl, err, response.status, { ...request, auth });
199
290
  next();
200
291
  reject(err);
201
292
  return;
202
293
  }
203
294
  }
204
295
  }
205
- await this.notifySuccessRequest(method, finalUrl, response);
296
+ await this.notifySuccessRequest(method, finalUrl, observerResponse, { ...request, auth });
206
297
  next();
207
298
  return resolve(result || undefined);
208
299
  };
@@ -285,15 +376,12 @@ class ApiHandler {
285
376
  next();
286
377
  await (0, common_1.delay)(wait);
287
378
  return this.request(method, url, {
288
- body: request.body,
289
- auth: request.auth,
290
- reason: request.reason,
291
- route: request.route,
379
+ ...request,
292
380
  unshift: true,
293
381
  });
294
382
  }
295
- async handle429(route, method, url, request, response, result, next, reject, now) {
296
- await this.onRatelimit?.(response, request);
383
+ async handle429(route, method, url, request, response, result, next, reject, now, notifyUrl) {
384
+ await this.notifyRatelimit(response, request, method, notifyUrl);
297
385
  const bucket = this.ratelimits.get(route);
298
386
  let retryAfter;
299
387
  const data = JSON.parse(result);
@@ -325,19 +413,13 @@ class ApiHandler {
325
413
  await (0, common_1.delay)(retryAfter);
326
414
  next();
327
415
  return this.request(method, url, {
328
- body: request.body,
329
- auth: request.auth,
330
- reason: request.reason,
331
- route: request.route,
416
+ ...request,
332
417
  unshift: true,
333
418
  });
334
419
  }
335
420
  next();
336
421
  return this.request(method, url, {
337
- body: request.body,
338
- auth: request.auth,
339
- reason: request.reason,
340
- route: request.route,
422
+ ...request,
341
423
  unshift: true,
342
424
  });
343
425
  }
@@ -389,7 +471,11 @@ class ApiHandler {
389
471
  let finalUrl = options.url;
390
472
  let data;
391
473
  if (options.request.auth) {
392
- options.headers.Authorization = `${this.options.type} ${options.request.token || this.options.token}`;
474
+ const token = options.request.token || this.options.token;
475
+ if (token === 'INVALID' || typeof token !== 'string' || token.length === 0) {
476
+ throw new common_1.SeyfertError('INVALID_TOKEN', { metadata: { detail: 'token is not a string' } });
477
+ }
478
+ options.headers.Authorization = `${this.options.type} ${token}`;
393
479
  }
394
480
  if (options.request.query) {
395
481
  const params = new URLSearchParams();
@@ -449,7 +535,7 @@ class ApiHandler {
449
535
  .replace(/^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/, '/webhooks/$1/:token');
450
536
  if (method === 'DELETE' && route.endsWith('/messages/:id')) {
451
537
  const messageID = url.slice(url.lastIndexOf('/') + 1);
452
- const createdAt = Number((0, common_1.snowflakeToTimestamp)(messageID));
538
+ const createdAt = (0, common_1.snowflakeToTimestamp)(messageID);
453
539
  if (Date.now() - createdAt >= 1000 * 60 * 60 * 24 * 14) {
454
540
  method += '_OLD';
455
541
  }
@@ -471,3 +557,40 @@ class ApiHandler {
471
557
  }
472
558
  }
473
559
  exports.ApiHandler = ApiHandler;
560
+ function freezeRestPayload(payload) {
561
+ return Object.freeze(payload);
562
+ }
563
+ function readonlyRequestOptions(request) {
564
+ const clone = deepClonePlain({
565
+ ...request,
566
+ body: request.body ? deepClonePlain(request.body) : undefined,
567
+ query: request.query ? deepClonePlain(request.query) : undefined,
568
+ files: request.files?.map(file => deepClonePlain(file)),
569
+ });
570
+ return deepFreezePlain(clone);
571
+ }
572
+ function deepClonePlain(value) {
573
+ if (Array.isArray(value))
574
+ return value.map(deepClonePlain);
575
+ if (!isPlainObject(value))
576
+ return value;
577
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, deepClonePlain(entry)]));
578
+ }
579
+ function deepFreezePlain(value) {
580
+ if (Array.isArray(value)) {
581
+ for (const entry of value)
582
+ deepFreezePlain(entry);
583
+ return Object.freeze(value);
584
+ }
585
+ if (!isPlainObject(value))
586
+ return value;
587
+ for (const entry of Object.values(value))
588
+ deepFreezePlain(entry);
589
+ return Object.freeze(value);
590
+ }
591
+ function isPlainObject(value) {
592
+ if (typeof value !== 'object' || value === null)
593
+ return false;
594
+ const prototype = Object.getPrototypeOf(value);
595
+ return prototype === Object.prototype || prototype === null;
596
+ }
@@ -1,4 +1,4 @@
1
- import type { MakeRequired } from '../common';
1
+ import type { Awaitable, MakeRequired } from '../common';
2
2
  export * from './api';
3
3
  export * from './utils/constants';
4
4
  export * from './utils/types';
@@ -34,3 +34,32 @@ export interface ApiRequestOptions {
34
34
  token?: string;
35
35
  }
36
36
  export type HttpMethods = 'GET' | 'DELETE' | 'PUT' | 'POST' | 'PATCH';
37
+ export interface RestObserverRequestPayload<TClient = unknown> {
38
+ readonly client: TClient;
39
+ readonly method: HttpMethods;
40
+ readonly url: `/${string}`;
41
+ readonly request: Readonly<ApiRequestOptions>;
42
+ }
43
+ export interface RestObserverSuccessPayload<TClient = unknown> extends RestObserverRequestPayload<TClient> {
44
+ readonly response: Response;
45
+ }
46
+ export interface RestObserverFailPayload<TClient = unknown> extends RestObserverRequestPayload<TClient> {
47
+ readonly error: unknown;
48
+ readonly statusCode?: number;
49
+ }
50
+ export interface RestObserverRatelimitPayload<TClient = unknown> extends RestObserverRequestPayload<TClient> {
51
+ readonly response: Response;
52
+ }
53
+ export interface RestObserver<TClient = unknown> {
54
+ onRequest?(payload: RestObserverRequestPayload<TClient>): Awaitable<unknown>;
55
+ onSuccess?(payload: RestObserverSuccessPayload<TClient>): Awaitable<unknown>;
56
+ onFail?(payload: RestObserverFailPayload<TClient>): Awaitable<unknown>;
57
+ onRatelimit?(payload: RestObserverRatelimitPayload<TClient>): Awaitable<unknown>;
58
+ }
59
+ export interface RestObserverEntry<TClient = unknown> {
60
+ readonly plugin?: string;
61
+ readonly observer: RestObserver<TClient>;
62
+ }
63
+ export interface RestObserveOptions {
64
+ }
65
+ export type RestObserverDisposer = () => void;
@@ -1,4 +1,4 @@
1
- export declare const DefaultUserAgent: "DiscordBot (https://seyfert.dev, v4.4.0)";
1
+ export declare const DefaultUserAgent: "DiscordBot (https://seyfert.dev, v4.0.0)";
2
2
  export declare const ALLOWED_EXTENSIONS: readonly ["webp", "png", "jpg", "jpeg", "gif"];
3
3
  export declare const ALLOWED_STICKER_EXTENSIONS: readonly ["png", "json", "gif"];
4
4
  export declare const ALLOWED_SIZES: readonly [16, 32, 64, 100, 128, 256, 512, 1024, 2048, 4096];
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALLOWED_TAG_BADGE_EXTENSIONS = exports.ALLOWED_SOUNDS_EXTENSIONS = exports.ALLOWED_SIZES = exports.ALLOWED_STICKER_EXTENSIONS = exports.ALLOWED_EXTENSIONS = exports.DefaultUserAgent = void 0;
4
- exports.DefaultUserAgent = 'DiscordBot (https://seyfert.dev, v4.4.0)';
4
+ exports.DefaultUserAgent = 'DiscordBot (https://seyfert.dev, v4.0.0)';
5
5
  exports.ALLOWED_EXTENSIONS = ['webp', 'png', 'jpg', 'jpeg', 'gif'];
6
6
  exports.ALLOWED_STICKER_EXTENSIONS = ['png', 'json', 'gif'];
7
7
  exports.ALLOWED_SIZES = [16, 32, 64, 100, 128, 256, 512, 1_024, 2_048, 4_096];
@@ -1,10 +1,11 @@
1
1
  import type { Snowflake } from '../../types';
2
+ import type { UserAvatarDefault } from '../Routes/cdn';
2
3
  /**
3
4
  * Calculates the default avatar index for a given user id.
4
5
  *
5
6
  * @param userId - The user id to calculate the default avatar index for
6
7
  */
7
- export declare function calculateUserDefaultAvatarIndex(userId: Snowflake, discriminator: string): number;
8
+ export declare function calculateUserDefaultAvatarIndex(userId: Snowflake, discriminator: string): UserAvatarDefault;
8
9
  /**
9
10
  * Verifies that a value is a buffer-like object.
10
11
  *
@@ -8,7 +8,7 @@ exports.isBufferLike = isBufferLike;
8
8
  * @param userId - The user id to calculate the default avatar index for
9
9
  */
10
10
  function calculateUserDefaultAvatarIndex(userId, discriminator) {
11
- return discriminator === '0' ? Number(BigInt(userId) >> 22n) % 6 : Number.parseInt(discriminator) % 5;
11
+ return (discriminator === '0' ? Number(BigInt(userId) >> 22n) % 6 : Number.parseInt(discriminator) % 5);
12
12
  }
13
13
  /**
14
14
  * Verifies that a value is a buffer-like object.
@@ -1,4 +1,4 @@
1
- import type { RestOrArray } from '../common';
1
+ import { type RestOrArray } from '../common';
2
2
  import { type APIMediaGalleryComponent, type APIMediaGalleryItems } from '../types';
3
3
  import { BaseComponentBuilder } from './Base';
4
4
  /**
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MediaGalleryItem = exports.MediaGallery = void 0;
4
+ const common_1 = require("../common");
4
5
  const types_1 = require("../types");
5
6
  const Base_1 = require("./Base");
6
7
  /**
@@ -104,6 +105,15 @@ class MediaGalleryItem {
104
105
  * @returns The JSON representation of the item data.
105
106
  */
106
107
  toJSON() {
108
+ if (!this.data.media)
109
+ throw new common_1.SeyfertError('MISSING_MEDIA', {
110
+ metadata: {
111
+ ...(0, common_1.createValidationMetadata)('media to be set before calling toJSON()', this.data.media, {
112
+ component: 'MediaGalleryItem',
113
+ }),
114
+ detail: 'Cannot convert to JSON without media.',
115
+ },
116
+ });
107
117
  return { ...this.data };
108
118
  }
109
119
  }
@@ -1,4 +1,4 @@
1
- import type { RestOrArray } from '../common';
1
+ import { type RestOrArray } from '../common';
2
2
  import { type APIModalInteractionResponseCallbackData, type APITextInputComponent, type TextInputStyle } from '../types';
3
3
  import { BaseComponentBuilder, type OptionValuesLength } from './Base';
4
4
  import type { ModalBuilderComponents, ModalSubmitCallback } from './types';
@@ -8,10 +8,16 @@ import type { ModalBuilderComponents, ModalSubmitCallback } from './types';
8
8
  * @example
9
9
  * const modal = new Modal();
10
10
  * modal.setTitle("Sample Modal");
11
+ * modal.setCustomId("sample-modal");
11
12
  * modal.addComponents(
12
- * new ActionRow<TextInput>()
13
- * .addComponents(new TextInput().setLabel("Enter text"))
14
- * ));
13
+ * new Label()
14
+ * .setLabel("Enter text")
15
+ * .setComponent(
16
+ * new TextInput()
17
+ * .setCustomId("sample-input")
18
+ * .setStyle(TextInputStyle.Paragraph)
19
+ * )
20
+ * );
15
21
  * modal.run((interaction) => {
16
22
  * // Handle modal submission
17
23
  * });
@@ -64,10 +70,14 @@ export declare class Modal {
64
70
  /**
65
71
  * Represents a text input component builder.
66
72
  * @example
67
- * const textInput = new TextInput().setLabel("Enter text");
68
- * textInput.setStyle(TextInputStyle.Paragraph);
69
- * textInput.setPlaceholder("Type here");
70
- * const json = textInput.toJSON();
73
+ * const textInput = new TextInput()
74
+ * .setCustomId("feedback")
75
+ * .setStyle(TextInputStyle.Paragraph)
76
+ * .setPlaceholder("Type here");
77
+ * const label = new Label()
78
+ * .setLabel("Feedback")
79
+ * .setComponent(textInput);
80
+ * const json = label.toJSON();
71
81
  */
72
82
  export declare class TextInput extends BaseComponentBuilder<APITextInputComponent> {
73
83
  /**
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TextInput = exports.Modal = void 0;
4
+ const common_1 = require("../common");
4
5
  const types_1 = require("../types");
5
6
  const _1 = require(".");
6
7
  const Base_1 = require("./Base");
@@ -10,10 +11,16 @@ const Base_1 = require("./Base");
10
11
  * @example
11
12
  * const modal = new Modal();
12
13
  * modal.setTitle("Sample Modal");
14
+ * modal.setCustomId("sample-modal");
13
15
  * modal.addComponents(
14
- * new ActionRow<TextInput>()
15
- * .addComponents(new TextInput().setLabel("Enter text"))
16
- * ));
16
+ * new Label()
17
+ * .setLabel("Enter text")
18
+ * .setComponent(
19
+ * new TextInput()
20
+ * .setCustomId("sample-input")
21
+ * .setStyle(TextInputStyle.Paragraph)
22
+ * )
23
+ * );
17
24
  * modal.run((interaction) => {
18
25
  * // Handle modal submission
19
26
  * });
@@ -82,6 +89,24 @@ class Modal {
82
89
  * @returns The modal data in JSON format.
83
90
  */
84
91
  toJSON() {
92
+ if (!this.data.custom_id)
93
+ throw new common_1.SeyfertError('MISSING_MODAL_CUSTOM_ID', {
94
+ metadata: {
95
+ ...(0, common_1.createValidationMetadata)('custom_id to be set before calling toJSON()', this.data.custom_id, {
96
+ component: 'Modal',
97
+ }),
98
+ detail: 'Cannot convert to JSON without a custom_id.',
99
+ },
100
+ });
101
+ if (!this.data.title)
102
+ throw new common_1.SeyfertError('MISSING_MODAL_TITLE', {
103
+ metadata: {
104
+ ...(0, common_1.createValidationMetadata)('title to be set before calling toJSON()', this.data.title, {
105
+ component: 'Modal',
106
+ }),
107
+ detail: 'Cannot convert to JSON without a title.',
108
+ },
109
+ });
85
110
  return {
86
111
  custom_id: this.data.custom_id,
87
112
  title: this.data.title,
@@ -93,10 +118,14 @@ exports.Modal = Modal;
93
118
  /**
94
119
  * Represents a text input component builder.
95
120
  * @example
96
- * const textInput = new TextInput().setLabel("Enter text");
97
- * textInput.setStyle(TextInputStyle.Paragraph);
98
- * textInput.setPlaceholder("Type here");
99
- * const json = textInput.toJSON();
121
+ * const textInput = new TextInput()
122
+ * .setCustomId("feedback")
123
+ * .setStyle(TextInputStyle.Paragraph)
124
+ * .setPlaceholder("Type here");
125
+ * const label = new Label()
126
+ * .setLabel("Feedback")
127
+ * .setComponent(textInput);
128
+ * const json = label.toJSON();
100
129
  */
101
130
  class TextInput extends Base_1.BaseComponentBuilder {
102
131
  /**
@@ -33,6 +33,24 @@ class PollBuilder {
33
33
  return this;
34
34
  }
35
35
  toJSON() {
36
+ if (!this.data.question?.text && !this.data.question?.emoji)
37
+ throw new common_1.SeyfertError('MISSING_POLL_QUESTION', {
38
+ metadata: {
39
+ ...(0, common_1.createValidationMetadata)('question to be set before calling toJSON()', this.data.question, {
40
+ component: 'PollBuilder',
41
+ }),
42
+ detail: 'Cannot convert to JSON without a question.',
43
+ },
44
+ });
45
+ if (!this.data.answers?.length)
46
+ throw new common_1.SeyfertError('MISSING_POLL_ANSWERS', {
47
+ metadata: {
48
+ ...(0, common_1.createValidationMetadata)('at least one answer to be set before calling toJSON()', this.data.answers, {
49
+ component: 'PollBuilder',
50
+ }),
51
+ detail: 'Cannot convert to JSON without answers.',
52
+ },
53
+ });
36
54
  return { ...this.data };
37
55
  }
38
56
  resolvedPollMedia(data) {