meocord 4.2.2 → 4.2.3
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/CHANGELOG.md +130 -0
- package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
- package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
- package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
- package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +432 -181
- package/dist/cjs/decorator/index.cjs +85 -120
- package/dist/cjs/testing/index.cjs +1664 -784
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +27 -5
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +17 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +83 -58
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +4 -4
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +44 -28
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +80 -53
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +5 -9
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +2 -1
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +28 -3
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +27 -29
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +6 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +2 -2
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +29 -13
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -355
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +195 -77
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +639 -113
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +1 -0
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +15 -4
- package/dist/types/common/index.d.cts +13 -9
- package/dist/types/common/index.d.ts +13 -9
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +137 -54
- package/dist/types/testing/index.d.ts +137 -54
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -1,53 +1,11 @@
|
|
|
1
|
-
import { GuildChannel, MessageReaction, MessageMentions, Message, GuildMember, BaseGuild, Guild, ModalSubmitInteraction, TextChannel, ThreadChannel, BaseGuildVoiceChannel, GuildPremiumTier, GuildVerificationLevel, GuildNSFWLevel, Locale, PresenceManager, ClientUser, UserFlagsBitField, User, GuildMemberFlagsBitField, GuildEmoji, Sticker, GuildScheduledEvent, AutoModerationRule, Invite, Role, Collection, MessageType, ReactionManager, ChatInputCommandInteraction, CommandInteraction, AutocompleteInteraction, ReactionUserManager, BaseInteraction, ChannelType, NewsChannel, VoiceChannel, ForumChannel, MediaChannel, DMChannel, BaseChannel, Client,
|
|
2
|
-
import {
|
|
1
|
+
import { GuildChannel, MessageReaction, MessageMentions, Message, GuildMember, BaseGuild, Guild, ModalSubmitInteraction, TextChannel, ThreadChannel, BaseGuildVoiceChannel, GuildPremiumTier, GuildVerificationLevel, GuildNSFWLevel, Locale, PresenceManager, ClientUser, UserFlagsBitField, User, GuildMemberFlagsBitField, GuildEmoji, Sticker, GuildScheduledEvent, AutoModerationRule, Invite, Role, Collection, MessageType, ReactionManager, ChatInputCommandInteraction, CommandInteraction, AutocompleteInteraction, ReactionUserManager, MessageComponentInteraction, PermissionsBitField, BaseInteraction, ChannelType, NewsChannel, VoiceChannel, ForumChannel, MediaChannel, StageChannel, CategoryChannel, DirectoryChannel, DMChannel, PartialGroupDMChannel, BaseChannel, Client, GuildMemberRoleManager, ComponentType, ButtonStyle, PermissionFlagsBits } from 'discord.js';
|
|
2
|
+
import { createModalFields } from './modal-fields.js';
|
|
3
3
|
import { nextSnowflake } from './snowflake.js';
|
|
4
4
|
import { strictMocks } from './strict-mocks.js';
|
|
5
5
|
import { Logger } from '../common/logger.js';
|
|
6
6
|
import { warnOnce } from '../common/deprecation.js';
|
|
7
7
|
|
|
8
8
|
const mockLogger = new Logger('Mocks');
|
|
9
|
-
/**
|
|
10
|
-
* A member's permissions where an interaction was made, as Discord computes them: under strict mocks, every permission
|
|
11
|
-
* for an Administrator or the owner, else the channel's overwrites on top of `member.permissions`, so a test that sets
|
|
12
|
-
* those keeps the base; otherwise `member.permissions`, with a warning once where strict mocks would read otherwise.
|
|
13
|
-
*/ function inChannel(interaction, member) {
|
|
14
|
-
const base = member.permissions;
|
|
15
|
-
// Only a server the mock caches has the overwrites to apply
|
|
16
|
-
const { guild } = interaction;
|
|
17
|
-
if (!(guild instanceof Guild)) return base;
|
|
18
|
-
const computed = base.has(PermissionFlagsBits.Administrator) || member.id === guild.ownerId ? new PermissionsBitField(PermissionsBitField.All).freeze() : withOverwrites(base, overwritingChannel(interaction, guild), member);
|
|
19
|
-
if (strictMocks()) return computed;
|
|
20
|
-
if (!computed.equals(base)) {
|
|
21
|
-
const lost = new PermissionsBitField(base).remove(computed).toArray();
|
|
22
|
-
const gained = new PermissionsBitField(computed).remove(base).toArray();
|
|
23
|
-
const change = lost.length > 0 ? [
|
|
24
|
-
`without ${lost.join(', ')}`
|
|
25
|
-
] : [];
|
|
26
|
-
if (gained.length > 0) change.push(`with ${gained.join(', ')}`);
|
|
27
|
-
warnOnce(mockLogger, `${interaction.constructor.name}.memberPermissions reads the member's own permissions here, without the ` + "channel's overwrites or every permission an Administrator or the owner has; in the next major version (5.0) " + `it reads them as Discord computes them in the channel, ${change.join(' and ')}. Set memberPermissions on the ` + 'mock, or call useStrictMocks() to compute them now.');
|
|
28
|
-
}
|
|
29
|
-
// Unless the test set them, a member's permissions come from its roles, @everyone's included
|
|
30
|
-
if (!Object.prototype.hasOwnProperty.call(member, 'permissions')) warnOnUnsetEveryone(interaction, guild, 'memberPermissions', base);
|
|
31
|
-
return base;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Warns once, as `key`, where `permissions` read a server's @everyone role default mode made with none, and lack what
|
|
35
|
-
* strict mocks give @everyone in a new server.
|
|
36
|
-
*/ function warnOnUnsetEveryone(interaction, guild, key, permissions) {
|
|
37
|
-
const everyone = guild.roles.everyone;
|
|
38
|
-
if (!everyone || !isUnsetEveryone(everyone) || permissions.has(NEW_SERVER_EVERYONE_PERMISSIONS)) return;
|
|
39
|
-
warnOnce(mockLogger, `${interaction.constructor.name}.${key} reads the server's @everyone role here with no permissions; in the next major ` + 'version (5.0) it has the permissions Discord gives @everyone in a new server, as strict mocks read it. Set ' + 'guild.roles.everyone.permissions, or call useStrictMocks() to read it so now.');
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* The channel whose overwrites apply where an interaction was made, read without making or caching one: a thread's
|
|
43
|
-
* parent, or undefined where the channel, or a thread's parent, is one the mock would make, which has none.
|
|
44
|
-
*/ function overwritingChannel(interaction, guild) {
|
|
45
|
-
const channel = heldChannelOf(interaction, guild);
|
|
46
|
-
// A thread's parent by the id the test gave it or one already read, so strict mocks' parent isn't made for this read
|
|
47
|
-
const parentId = (thread)=>Object.prototype.hasOwnProperty.call(thread, 'parentId') ? thread.parentId : madeParentIds.get(thread);
|
|
48
|
-
const at = channel instanceof ThreadChannel ? guild.channels.cache.get(parentId(channel) ?? '') : channel;
|
|
49
|
-
return at instanceof GuildChannel ? at : undefined;
|
|
50
|
-
}
|
|
51
9
|
// The parent id strict mocks made for each thread made without one, once read
|
|
52
10
|
const madeParentIds = new WeakMap();
|
|
53
11
|
// Private in discord.js's typings, though they are what its own permissionsFor computes with; called on the prototype
|
|
@@ -65,305 +23,7 @@ const channelPermissions = GuildChannel.prototype;
|
|
|
65
23
|
Class.prototype,
|
|
66
24
|
defaults
|
|
67
25
|
];
|
|
68
|
-
/**
|
|
69
|
-
* What a mock reads for a data property discord.js always sets, where the test gave none: a value in Discord's shape,
|
|
70
|
-
* such as `'en-US'` for a locale, `null` for what may be absent and `false` for a flag, or the property's own getter
|
|
71
|
-
* for one discord.js computes. The classes a mock descends from are read nearest first.
|
|
72
|
-
*/ const DEFAULTS = new Map([
|
|
73
|
-
defaultsOf(Guild, {
|
|
74
|
-
name: ()=>'Guild',
|
|
75
|
-
preferredLocale: ()=>Locale.EnglishUS,
|
|
76
|
-
ownerId: nextSnowflake,
|
|
77
|
-
memberCount: ()=>1,
|
|
78
|
-
available: ()=>true,
|
|
79
|
-
large: ()=>false,
|
|
80
|
-
features: ()=>[],
|
|
81
|
-
description: ()=>null,
|
|
82
|
-
icon: ()=>null,
|
|
83
|
-
banner: ()=>null,
|
|
84
|
-
splash: ()=>null,
|
|
85
|
-
systemChannelId: ()=>null,
|
|
86
|
-
shardId: ()=>0,
|
|
87
|
-
nsfwLevel: ()=>GuildNSFWLevel.Default,
|
|
88
|
-
verificationLevel: ()=>GuildVerificationLevel.None,
|
|
89
|
-
presences: (guild)=>ownedManager(PresenceManager.prototype, {
|
|
90
|
-
guild
|
|
91
|
-
}),
|
|
92
|
-
premiumTier: ()=>GuildPremiumTier.None,
|
|
93
|
-
createdTimestamp: REAL_GETTER,
|
|
94
|
-
createdAt: REAL_GETTER
|
|
95
|
-
}),
|
|
96
|
-
defaultsOf(ClientUser, {
|
|
97
|
-
username: ()=>'bot'
|
|
98
|
-
}),
|
|
99
|
-
defaultsOf(User, {
|
|
100
|
-
username: ()=>'user',
|
|
101
|
-
bot: ()=>false,
|
|
102
|
-
globalName: ()=>null,
|
|
103
|
-
discriminator: ()=>'0',
|
|
104
|
-
avatar: ()=>null,
|
|
105
|
-
system: ()=>false,
|
|
106
|
-
flags: ()=>new UserFlagsBitField(),
|
|
107
|
-
tag: REAL_GETTER,
|
|
108
|
-
displayName: REAL_GETTER,
|
|
109
|
-
defaultAvatarURL: REAL_GETTER,
|
|
110
|
-
createdTimestamp: REAL_GETTER,
|
|
111
|
-
createdAt: REAL_GETTER
|
|
112
|
-
}),
|
|
113
|
-
defaultsOf(GuildMember, {
|
|
114
|
-
user: ()=>createMockUser(),
|
|
115
|
-
id: REAL_GETTER,
|
|
116
|
-
nickname: ()=>null,
|
|
117
|
-
avatar: ()=>null,
|
|
118
|
-
// In the server since the mock was made, as a member Discord sends always is
|
|
119
|
-
joinedTimestamp: ()=>Date.now(),
|
|
120
|
-
joinedAt: REAL_GETTER,
|
|
121
|
-
partial: REAL_GETTER,
|
|
122
|
-
premiumSince: REAL_GETTER,
|
|
123
|
-
communicationDisabledUntil: REAL_GETTER,
|
|
124
|
-
premiumSinceTimestamp: ()=>null,
|
|
125
|
-
communicationDisabledUntilTimestamp: ()=>null,
|
|
126
|
-
pending: ()=>false,
|
|
127
|
-
flags: ()=>new GuildMemberFlagsBitField().freeze(),
|
|
128
|
-
displayName: REAL_GETTER,
|
|
129
|
-
roles: (member)=>memberRoles(member, []),
|
|
130
|
-
permissions: REAL_GETTER
|
|
131
|
-
}),
|
|
132
|
-
// A server's emoji, sticker, scheduled event and AutoMod rule are named, and what discord.js formats from them
|
|
133
|
-
// computes: an emoji's mention and identifier, an event's and an invite's link
|
|
134
|
-
defaultsOf(GuildEmoji, {
|
|
135
|
-
name: ()=>'emoji',
|
|
136
|
-
animated: ()=>false,
|
|
137
|
-
identifier: REAL_GETTER
|
|
138
|
-
}),
|
|
139
|
-
defaultsOf(Sticker, {
|
|
140
|
-
name: ()=>'sticker'
|
|
141
|
-
}),
|
|
142
|
-
defaultsOf(GuildScheduledEvent, {
|
|
143
|
-
name: ()=>'event',
|
|
144
|
-
url: REAL_GETTER
|
|
145
|
-
}),
|
|
146
|
-
defaultsOf(AutoModerationRule, {
|
|
147
|
-
name: ()=>'rule'
|
|
148
|
-
}),
|
|
149
|
-
defaultsOf(Invite, {
|
|
150
|
-
url: REAL_GETTER
|
|
151
|
-
}),
|
|
152
|
-
defaultsOf(Role, {
|
|
153
|
-
name: ()=>'role',
|
|
154
|
-
// Above @everyone, which a server's role manager makes at position 0
|
|
155
|
-
position: ()=>1,
|
|
156
|
-
// Every role has a server; one made without is in one of its own, unless a guild given it takes it in
|
|
157
|
-
guild: ()=>createMockGuild(),
|
|
158
|
-
rawPosition: (role)=>role.position,
|
|
159
|
-
permissions: noPermissions,
|
|
160
|
-
hoist: ()=>false,
|
|
161
|
-
managed: ()=>false,
|
|
162
|
-
mentionable: ()=>false,
|
|
163
|
-
icon: ()=>null,
|
|
164
|
-
unicodeEmoji: ()=>null,
|
|
165
|
-
tags: ()=>null,
|
|
166
|
-
colors: ()=>({
|
|
167
|
-
primaryColor: 0,
|
|
168
|
-
secondaryColor: null,
|
|
169
|
-
tertiaryColor: null
|
|
170
|
-
}),
|
|
171
|
-
hexColor: REAL_GETTER,
|
|
172
|
-
createdTimestamp: REAL_GETTER,
|
|
173
|
-
createdAt: REAL_GETTER
|
|
174
|
-
}),
|
|
175
|
-
// A message's text says which handler it is for, so it stays the test's to give, as a command's name does
|
|
176
|
-
defaultsOf(Message, {
|
|
177
|
-
pinned: ()=>false,
|
|
178
|
-
tts: ()=>false,
|
|
179
|
-
type: ()=>MessageType.Default,
|
|
180
|
-
system: ()=>false,
|
|
181
|
-
webhookId: ()=>null,
|
|
182
|
-
applicationId: ()=>null,
|
|
183
|
-
nonce: ()=>null,
|
|
184
|
-
position: ()=>null,
|
|
185
|
-
activity: ()=>null,
|
|
186
|
-
reference: ()=>null,
|
|
187
|
-
poll: ()=>null,
|
|
188
|
-
interactionMetadata: ()=>null,
|
|
189
|
-
stickers: ()=>new Collection(),
|
|
190
|
-
messageSnapshots: ()=>new Collection(),
|
|
191
|
-
reactions: (message)=>ownedManager(ReactionManager.prototype, {
|
|
192
|
-
message
|
|
193
|
-
}),
|
|
194
|
-
url: REAL_GETTER
|
|
195
|
-
}),
|
|
196
|
-
// A command's name and a component's customId say which handler a call is for, so they stay the test's to give
|
|
197
|
-
defaultsOf(ChatInputCommandInteraction, {
|
|
198
|
-
options: ()=>createChatInputOptions()
|
|
199
|
-
}),
|
|
200
|
-
defaultsOf(CommandInteraction, {
|
|
201
|
-
commandId: nextSnowflake,
|
|
202
|
-
commandGuildId: ()=>null
|
|
203
|
-
}),
|
|
204
|
-
defaultsOf(AutocompleteInteraction, {
|
|
205
|
-
commandId: nextSnowflake,
|
|
206
|
-
commandGuildId: ()=>null,
|
|
207
|
-
options: ()=>createChatInputOptions()
|
|
208
|
-
}),
|
|
209
|
-
defaultsOf(MessageReaction, {
|
|
210
|
-
users: (reaction)=>ownedManager(ReactionUserManager.prototype, {
|
|
211
|
-
reaction
|
|
212
|
-
})
|
|
213
|
-
}),
|
|
214
|
-
defaultsOf(BaseInteraction, {
|
|
215
|
-
applicationId: nextSnowflake,
|
|
216
|
-
token: ()=>'mock-interaction-token',
|
|
217
|
-
version: ()=>1,
|
|
218
|
-
context: ()=>null,
|
|
219
|
-
entitlements: ()=>new Collection(),
|
|
220
|
-
// What Discord sends with an interaction in a server, the member's permissions there; typed for a cached one
|
|
221
|
-
// Each reads where the interaction is as its own, and warns by its own name where strict mocks place it otherwise
|
|
222
|
-
memberPermissions: (interaction)=>readPlaceAs(interaction, 'memberPermissions', ()=>{
|
|
223
|
-
const { guildId } = interaction;
|
|
224
|
-
const member = interaction.member;
|
|
225
|
-
if (!guildId) return null;
|
|
226
|
-
if (member instanceof GuildMember) return inChannel(interaction, member);
|
|
227
|
-
// A raw member's permissions are the string Discord sends, read as discord.js reads them
|
|
228
|
-
return isRawMember(member) && !interaction.guild ? new PermissionsBitField(BigInt(member.permissions)).freeze() : null;
|
|
229
|
-
}),
|
|
230
|
-
// The bot's permissions where the interaction was made: its member's in the channel, or in a thread its parent, as
|
|
231
|
-
// discord.js computes them; none outside a server
|
|
232
|
-
appPermissions: (interaction)=>readPlaceAs(interaction, 'appPermissions', ()=>{
|
|
233
|
-
const { guild } = interaction;
|
|
234
|
-
const me = guild instanceof Guild ? botMemberOf(guild) : null;
|
|
235
|
-
if (!(me instanceof GuildMember) || heldChannelOf(interaction, guild) === null) return noPermissions();
|
|
236
|
-
// discord.js's own computation, given its own overwritesFor over a test's stub on the channel; a channel the
|
|
237
|
-
// mock would make has no overwrites
|
|
238
|
-
const at = overwritingChannel(interaction, guild) ?? {
|
|
239
|
-
guild,
|
|
240
|
-
permissionOverwrites: {
|
|
241
|
-
cache: new Collection()
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
const channel = Object.create(at, {
|
|
245
|
-
overwritesFor: {
|
|
246
|
-
value: channelPermissions.overwritesFor
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
const permissions = channelPermissions.memberPermissions.call(channel, me, true);
|
|
250
|
-
// The bot's member has @everyone's too
|
|
251
|
-
warnOnUnsetEveryone(interaction, guild, 'appPermissions', permissions);
|
|
252
|
-
return permissions;
|
|
253
|
-
}, // A server the bot isn't in has no guild in either mode, so no permissions in both
|
|
254
|
-
(place)=>!place.raw)
|
|
255
|
-
}),
|
|
256
|
-
defaultsOf(TextChannel, {
|
|
257
|
-
type: ()=>ChannelType.GuildText,
|
|
258
|
-
nsfw: ()=>false,
|
|
259
|
-
topic: ()=>null,
|
|
260
|
-
rateLimitPerUser: ()=>0
|
|
261
|
-
}),
|
|
262
|
-
defaultsOf(NewsChannel, {
|
|
263
|
-
type: ()=>ChannelType.GuildAnnouncement,
|
|
264
|
-
nsfw: ()=>false,
|
|
265
|
-
topic: ()=>null
|
|
266
|
-
}),
|
|
267
|
-
defaultsOf(VoiceChannel, {
|
|
268
|
-
type: ()=>ChannelType.GuildVoice,
|
|
269
|
-
nsfw: ()=>false
|
|
270
|
-
}),
|
|
271
|
-
defaultsOf(ForumChannel, {
|
|
272
|
-
type: ()=>ChannelType.GuildForum,
|
|
273
|
-
nsfw: ()=>false,
|
|
274
|
-
topic: ()=>null
|
|
275
|
-
}),
|
|
276
|
-
defaultsOf(MediaChannel, {
|
|
277
|
-
type: ()=>ChannelType.GuildMedia,
|
|
278
|
-
nsfw: ()=>false,
|
|
279
|
-
topic: ()=>null
|
|
280
|
-
}),
|
|
281
|
-
defaultsOf(ThreadChannel, {
|
|
282
|
-
type: ()=>ChannelType.PublicThread,
|
|
283
|
-
name: ()=>'thread'
|
|
284
|
-
}),
|
|
285
|
-
defaultsOf(DMChannel, {
|
|
286
|
-
type: ()=>ChannelType.DM
|
|
287
|
-
}),
|
|
288
|
-
defaultsOf(GuildChannel, {
|
|
289
|
-
name: ()=>'channel',
|
|
290
|
-
guildId: (channel)=>typeof channel.guild?.id === 'string' ? channel.guild.id : nextSnowflake(),
|
|
291
|
-
position: ()=>0,
|
|
292
|
-
rawPosition: ()=>0,
|
|
293
|
-
parentId: ()=>null
|
|
294
|
-
}),
|
|
295
|
-
defaultsOf(BaseChannel, {
|
|
296
|
-
createdTimestamp: REAL_GETTER,
|
|
297
|
-
createdAt: REAL_GETTER
|
|
298
|
-
}),
|
|
299
|
-
defaultsOf(Client, {
|
|
300
|
-
readyTimestamp: ()=>Date.now(),
|
|
301
|
-
readyAt: REAL_GETTER,
|
|
302
|
-
uptime: REAL_GETTER,
|
|
303
|
-
token: ()=>null
|
|
304
|
-
}),
|
|
305
|
-
defaultsOf(BaseGuild, {
|
|
306
|
-
nameAcronym: REAL_GETTER
|
|
307
|
-
})
|
|
308
|
-
]);
|
|
309
|
-
/**
|
|
310
|
-
* What strict mocks also read as Discord sends it, for discord.js's getters that read it: a server for a channel or
|
|
311
|
-
* thread made without one, a thread's parent text channel in it, and data that is otherwise a placeholder.
|
|
312
|
-
*/ const STRICT_DEFAULTS = new Map([
|
|
313
|
-
defaultsOf(Message, {
|
|
314
|
-
content: ()=>'',
|
|
315
|
-
editedTimestamp: ()=>null,
|
|
316
|
-
editedAt: REAL_GETTER
|
|
317
|
-
}),
|
|
318
|
-
defaultsOf(MessageMentions, {
|
|
319
|
-
repliedUser: ()=>null
|
|
320
|
-
}),
|
|
321
|
-
defaultsOf(Guild, {
|
|
322
|
-
rulesChannelId: ()=>null,
|
|
323
|
-
publicUpdatesChannelId: ()=>null,
|
|
324
|
-
systemChannel: REAL_GETTER
|
|
325
|
-
}),
|
|
326
|
-
defaultsOf(BaseGuild, {
|
|
327
|
-
verified: REAL_GETTER
|
|
328
|
-
}),
|
|
329
|
-
defaultsOf(GuildMember, {
|
|
330
|
-
presence: REAL_GETTER
|
|
331
|
-
}),
|
|
332
|
-
defaultsOf(ModalSubmitInteraction, {
|
|
333
|
-
message: ()=>null
|
|
334
|
-
}),
|
|
335
|
-
defaultsOf(ThreadChannel, {
|
|
336
|
-
guild: ()=>createMockGuild(),
|
|
337
|
-
guildId: (thread)=>thread.guild.id,
|
|
338
|
-
parentId: (thread)=>{
|
|
339
|
-
const parent = createMockChannel(TextChannel, {
|
|
340
|
-
guild: thread.guild
|
|
341
|
-
});
|
|
342
|
-
thread.guild.channels.cache.set(parent.id, parent);
|
|
343
|
-
madeParentIds.set(thread, parent.id);
|
|
344
|
-
return parent.id;
|
|
345
|
-
},
|
|
346
|
-
parent: REAL_GETTER,
|
|
347
|
-
ownerId: nextSnowflake,
|
|
348
|
-
archived: ()=>false,
|
|
349
|
-
locked: ()=>false
|
|
350
|
-
}),
|
|
351
|
-
defaultsOf(GuildChannel, {
|
|
352
|
-
guild: ()=>createMockGuild(),
|
|
353
|
-
parent: REAL_GETTER
|
|
354
|
-
}),
|
|
355
|
-
// Full once its members reach a user limit, which a voice channel has none of unless given one
|
|
356
|
-
defaultsOf(BaseGuildVoiceChannel, {
|
|
357
|
-
userLimit: ()=>0,
|
|
358
|
-
full: REAL_GETTER
|
|
359
|
-
}),
|
|
360
|
-
// A reaction's message, as discord.js always has one, is a whole one
|
|
361
|
-
defaultsOf(MessageReaction, {
|
|
362
|
-
count: ()=>1,
|
|
363
|
-
me: ()=>false,
|
|
364
|
-
message: ()=>createMockMessage()
|
|
365
|
-
})
|
|
366
|
-
]);
|
|
26
|
+
/** A channel's or thread's server id, its guild's, as discord.js sets it; one of its own for a guild the mock lacks. */ const guildIdOf = (channel)=>typeof channel.guild?.id === 'string' ? channel.guild.id : nextSnowflake();
|
|
367
27
|
/**
|
|
368
28
|
* What a mock reads as a placeholder unless strict mocks read it as discord.js gives it, by the value a test sets for
|
|
369
29
|
* that, which the warning on reading one names.
|
|
@@ -425,19 +85,453 @@ const channelPermissions = GuildChannel.prototype;
|
|
|
425
85
|
}
|
|
426
86
|
return undefined;
|
|
427
87
|
}
|
|
428
|
-
/**
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The defaults a mock reads for a property the test gave none, made with `makers`: for `key` on a mock of `target`'s
|
|
90
|
+
* class, the default of the nearest class that has one, strict ones first.
|
|
91
|
+
*/ function discordDefaults(makers) {
|
|
92
|
+
const { botMemberOf, createChatInputOptions, createMockChannel, createMockGuild, createMockMessage, createMockUser, heldChannelOf, isRawMember, isUnsetEveryone, madeAt, memberRoles, NEW_SERVER_EVERYONE_PERMISSIONS, ownedManager, readPlaceAs, readPlaceQuietly } = makers;
|
|
93
|
+
/**
|
|
94
|
+
* A member's permissions where an interaction was made, as Discord computes them: under strict mocks, every permission
|
|
95
|
+
* for an Administrator or the owner, else the channel's overwrites on top of `member.permissions`, so a test that sets
|
|
96
|
+
* those keeps the base; otherwise `member.permissions`, with a warning once where strict mocks would read otherwise.
|
|
97
|
+
*/ function inChannel(interaction, member) {
|
|
98
|
+
const base = member.permissions;
|
|
99
|
+
// Only a server the mock caches has the overwrites to apply
|
|
100
|
+
const { guild } = interaction;
|
|
101
|
+
if (!(guild instanceof Guild)) return base;
|
|
102
|
+
const computed = base.has(PermissionFlagsBits.Administrator) || member.id === guild.ownerId ? new PermissionsBitField(PermissionsBitField.All).freeze() : withOverwrites(base, overwritingChannel(interaction, guild), member);
|
|
103
|
+
if (strictMocks()) return computed;
|
|
104
|
+
if (!computed.equals(base)) {
|
|
105
|
+
const lost = new PermissionsBitField(base).remove(computed).toArray();
|
|
106
|
+
const gained = new PermissionsBitField(computed).remove(base).toArray();
|
|
107
|
+
const change = lost.length > 0 ? [
|
|
108
|
+
`without ${lost.join(', ')}`
|
|
109
|
+
] : [];
|
|
110
|
+
if (gained.length > 0) change.push(`with ${gained.join(', ')}`);
|
|
111
|
+
warnOnce(mockLogger, `${interaction.constructor.name}.memberPermissions reads the member's own permissions here, without the ` + "channel's overwrites or every permission an Administrator or the owner has; in the next major version (5.0) " + `it reads them as Discord computes them in the channel, ${change.join(' and ')}. Set memberPermissions on the ` + 'mock, or call useStrictMocks() to compute them now.');
|
|
438
112
|
}
|
|
113
|
+
// Unless the test set them, a member's permissions come from its roles, @everyone's included
|
|
114
|
+
if (!Object.prototype.hasOwnProperty.call(member, 'permissions')) warnOnUnsetEveryone(interaction, guild, 'memberPermissions', base);
|
|
115
|
+
return base;
|
|
439
116
|
}
|
|
440
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Warns once, as `key`, where `permissions` read a server's @everyone role default mode made with none, and lack what
|
|
119
|
+
* strict mocks give @everyone in a new server.
|
|
120
|
+
*/ function warnOnUnsetEveryone(interaction, guild, key, permissions) {
|
|
121
|
+
const everyone = guild.roles.everyone;
|
|
122
|
+
if (!everyone || !isUnsetEveryone(everyone) || permissions.has(NEW_SERVER_EVERYONE_PERMISSIONS)) return;
|
|
123
|
+
warnOnce(mockLogger, `${interaction.constructor.name}.${key} reads the server's @everyone role here with no permissions; in the next major ` + 'version (5.0) it has the permissions Discord gives @everyone in a new server, as strict mocks read it. Set ' + 'guild.roles.everyone.permissions, or call useStrictMocks() to read it so now.');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The channel whose overwrites apply where an interaction was made, read without making or caching one: a thread's
|
|
127
|
+
* parent, or undefined where the channel, or a thread's parent, is one the mock would make, which has none.
|
|
128
|
+
*/ function overwritingChannel(interaction, guild) {
|
|
129
|
+
const channel = heldChannelOf(interaction, guild);
|
|
130
|
+
// A thread's parent by the id the test gave it or one already read, so strict mocks' parent isn't made for this read
|
|
131
|
+
const parentId = (thread)=>Object.prototype.hasOwnProperty.call(thread, 'parentId') ? thread.parentId : madeParentIds.get(thread);
|
|
132
|
+
const at = channel instanceof ThreadChannel ? guild.channels.cache.get(parentId(channel) ?? '') : channel;
|
|
133
|
+
return at instanceof GuildChannel ? at : undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A member's join or a client's ready time: under strict mocks when the mock was made, as Discord sends it, so time a
|
|
137
|
+
* test advances afterwards counts. Default mode stamps it at the first read and warns once.
|
|
138
|
+
*/ function stampedAt(mock, name) {
|
|
139
|
+
if (strictMocks()) return madeAt(mock);
|
|
140
|
+
warnOnce(mockLogger, `${name} here is the time it was first read; under useStrictMocks() it is the time the mock was made, so time a ` + 'test advances afterwards counts.');
|
|
141
|
+
return Date.now();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The server of a channel or thread made without one: the one its own guildId names, else one of its own. Its own
|
|
145
|
+
* value is read, not its default, which reads the guild in turn.
|
|
146
|
+
*/ function guildFromOwnId(channel) {
|
|
147
|
+
const id = Object.getOwnPropertyDescriptor(channel, 'guildId')?.value;
|
|
148
|
+
return createMockGuild(typeof id === 'string' ? {
|
|
149
|
+
id
|
|
150
|
+
} : {});
|
|
151
|
+
}
|
|
152
|
+
/** The bot's message a component interaction was used on, in the interaction's channel, holding that component. */ function clickedMessage(interaction) {
|
|
153
|
+
const channel = readPlaceQuietly(()=>interaction.channel);
|
|
154
|
+
const bot = interaction.client?.user;
|
|
155
|
+
const { componentType: type } = interaction;
|
|
156
|
+
// A placeholder customId, for an interaction made without one, has no place in the component's data
|
|
157
|
+
const customId = typeof interaction.customId === 'string' ? interaction.customId : undefined;
|
|
158
|
+
const component = type === ComponentType.Button ? {
|
|
159
|
+
type,
|
|
160
|
+
custom_id: customId,
|
|
161
|
+
style: ButtonStyle.Primary
|
|
162
|
+
} : type === ComponentType.StringSelect ? {
|
|
163
|
+
type,
|
|
164
|
+
custom_id: customId,
|
|
165
|
+
options: []
|
|
166
|
+
} : typeof type === 'number' ? {
|
|
167
|
+
type,
|
|
168
|
+
custom_id: customId
|
|
169
|
+
} : undefined;
|
|
170
|
+
return createMockMessage({
|
|
171
|
+
...channel ? {
|
|
172
|
+
channel: channel
|
|
173
|
+
} : {},
|
|
174
|
+
...bot instanceof User ? {
|
|
175
|
+
author: bot
|
|
176
|
+
} : {},
|
|
177
|
+
components: component ? [
|
|
178
|
+
{
|
|
179
|
+
type: ComponentType.ActionRow,
|
|
180
|
+
components: [
|
|
181
|
+
component
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
] : []
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* What a mock reads for a data property discord.js always sets, where the test gave none: a value in Discord's shape,
|
|
189
|
+
* such as `'en-US'` for a locale, `null` for what may be absent and `false` for a flag, or the property's own getter
|
|
190
|
+
* for one discord.js computes. The classes a mock descends from are read nearest first.
|
|
191
|
+
*/ const DEFAULTS = new Map([
|
|
192
|
+
defaultsOf(Guild, {
|
|
193
|
+
name: ()=>'Guild',
|
|
194
|
+
preferredLocale: ()=>Locale.EnglishUS,
|
|
195
|
+
ownerId: nextSnowflake,
|
|
196
|
+
memberCount: ()=>1,
|
|
197
|
+
available: ()=>true,
|
|
198
|
+
large: ()=>false,
|
|
199
|
+
features: ()=>[],
|
|
200
|
+
description: ()=>null,
|
|
201
|
+
icon: ()=>null,
|
|
202
|
+
banner: ()=>null,
|
|
203
|
+
splash: ()=>null,
|
|
204
|
+
systemChannelId: ()=>null,
|
|
205
|
+
shardId: ()=>0,
|
|
206
|
+
nsfwLevel: ()=>GuildNSFWLevel.Default,
|
|
207
|
+
verificationLevel: ()=>GuildVerificationLevel.None,
|
|
208
|
+
presences: (guild)=>ownedManager(PresenceManager.prototype, {
|
|
209
|
+
guild
|
|
210
|
+
}),
|
|
211
|
+
premiumTier: ()=>GuildPremiumTier.None,
|
|
212
|
+
createdTimestamp: REAL_GETTER,
|
|
213
|
+
createdAt: REAL_GETTER
|
|
214
|
+
}),
|
|
215
|
+
defaultsOf(ClientUser, {
|
|
216
|
+
username: ()=>'bot'
|
|
217
|
+
}),
|
|
218
|
+
defaultsOf(User, {
|
|
219
|
+
username: ()=>'user',
|
|
220
|
+
bot: ()=>false,
|
|
221
|
+
globalName: ()=>null,
|
|
222
|
+
discriminator: ()=>'0',
|
|
223
|
+
avatar: ()=>null,
|
|
224
|
+
system: ()=>false,
|
|
225
|
+
flags: ()=>new UserFlagsBitField(),
|
|
226
|
+
tag: REAL_GETTER,
|
|
227
|
+
displayName: REAL_GETTER,
|
|
228
|
+
defaultAvatarURL: REAL_GETTER,
|
|
229
|
+
createdTimestamp: REAL_GETTER,
|
|
230
|
+
createdAt: REAL_GETTER
|
|
231
|
+
}),
|
|
232
|
+
defaultsOf(GuildMember, {
|
|
233
|
+
user: ()=>createMockUser(),
|
|
234
|
+
id: REAL_GETTER,
|
|
235
|
+
nickname: ()=>null,
|
|
236
|
+
avatar: ()=>null,
|
|
237
|
+
joinedTimestamp: (member)=>stampedAt(member, 'GuildMember.joinedTimestamp'),
|
|
238
|
+
joinedAt: REAL_GETTER,
|
|
239
|
+
partial: REAL_GETTER,
|
|
240
|
+
premiumSince: REAL_GETTER,
|
|
241
|
+
communicationDisabledUntil: REAL_GETTER,
|
|
242
|
+
premiumSinceTimestamp: ()=>null,
|
|
243
|
+
communicationDisabledUntilTimestamp: ()=>null,
|
|
244
|
+
pending: ()=>false,
|
|
245
|
+
flags: ()=>new GuildMemberFlagsBitField().freeze(),
|
|
246
|
+
displayName: REAL_GETTER,
|
|
247
|
+
// The colour of its highest coloured role, as discord.js computes it from the roles in its cache
|
|
248
|
+
displayColor: REAL_GETTER,
|
|
249
|
+
displayHexColor: REAL_GETTER,
|
|
250
|
+
roles: (member)=>memberRoles(member, []),
|
|
251
|
+
permissions: REAL_GETTER
|
|
252
|
+
}),
|
|
253
|
+
// A server's emoji, sticker, scheduled event and AutoMod rule are named, and what discord.js formats from them
|
|
254
|
+
// computes: an emoji's mention and identifier, an event's and an invite's link
|
|
255
|
+
defaultsOf(GuildEmoji, {
|
|
256
|
+
name: ()=>'emoji',
|
|
257
|
+
animated: ()=>false,
|
|
258
|
+
identifier: REAL_GETTER
|
|
259
|
+
}),
|
|
260
|
+
defaultsOf(Sticker, {
|
|
261
|
+
name: ()=>'sticker'
|
|
262
|
+
}),
|
|
263
|
+
defaultsOf(GuildScheduledEvent, {
|
|
264
|
+
name: ()=>'event',
|
|
265
|
+
url: REAL_GETTER
|
|
266
|
+
}),
|
|
267
|
+
defaultsOf(AutoModerationRule, {
|
|
268
|
+
name: ()=>'rule'
|
|
269
|
+
}),
|
|
270
|
+
defaultsOf(Invite, {
|
|
271
|
+
url: REAL_GETTER
|
|
272
|
+
}),
|
|
273
|
+
defaultsOf(Role, {
|
|
274
|
+
name: ()=>'role',
|
|
275
|
+
// Above @everyone, which a server's role manager makes at position 0
|
|
276
|
+
position: ()=>1,
|
|
277
|
+
// Every role has a server; one made without is in one of its own, unless a guild given it takes it in
|
|
278
|
+
guild: ()=>createMockGuild(),
|
|
279
|
+
rawPosition: (role)=>role.position,
|
|
280
|
+
permissions: noPermissions,
|
|
281
|
+
hoist: ()=>false,
|
|
282
|
+
managed: ()=>false,
|
|
283
|
+
mentionable: ()=>false,
|
|
284
|
+
icon: ()=>null,
|
|
285
|
+
unicodeEmoji: ()=>null,
|
|
286
|
+
tags: ()=>null,
|
|
287
|
+
colors: ()=>({
|
|
288
|
+
primaryColor: 0,
|
|
289
|
+
secondaryColor: null,
|
|
290
|
+
tertiaryColor: null
|
|
291
|
+
}),
|
|
292
|
+
hexColor: REAL_GETTER,
|
|
293
|
+
createdTimestamp: REAL_GETTER,
|
|
294
|
+
createdAt: REAL_GETTER
|
|
295
|
+
}),
|
|
296
|
+
// A message's text says which handler it is for, so it stays the test's to give, as a command's name does
|
|
297
|
+
defaultsOf(Message, {
|
|
298
|
+
pinned: ()=>false,
|
|
299
|
+
tts: ()=>false,
|
|
300
|
+
type: ()=>MessageType.Default,
|
|
301
|
+
system: ()=>false,
|
|
302
|
+
webhookId: ()=>null,
|
|
303
|
+
applicationId: ()=>null,
|
|
304
|
+
nonce: ()=>null,
|
|
305
|
+
position: ()=>null,
|
|
306
|
+
activity: ()=>null,
|
|
307
|
+
reference: ()=>null,
|
|
308
|
+
poll: ()=>null,
|
|
309
|
+
interactionMetadata: ()=>null,
|
|
310
|
+
stickers: ()=>new Collection(),
|
|
311
|
+
messageSnapshots: ()=>new Collection(),
|
|
312
|
+
reactions: (message)=>ownedManager(ReactionManager.prototype, {
|
|
313
|
+
message
|
|
314
|
+
}),
|
|
315
|
+
url: REAL_GETTER
|
|
316
|
+
}),
|
|
317
|
+
// A command's name and a component's customId say which handler a call is for, so they stay the test's to give
|
|
318
|
+
defaultsOf(ChatInputCommandInteraction, {
|
|
319
|
+
options: ()=>createChatInputOptions()
|
|
320
|
+
}),
|
|
321
|
+
defaultsOf(CommandInteraction, {
|
|
322
|
+
commandId: nextSnowflake,
|
|
323
|
+
commandGuildId: ()=>null
|
|
324
|
+
}),
|
|
325
|
+
defaultsOf(AutocompleteInteraction, {
|
|
326
|
+
commandId: nextSnowflake,
|
|
327
|
+
commandGuildId: ()=>null,
|
|
328
|
+
options: ()=>createChatInputOptions()
|
|
329
|
+
}),
|
|
330
|
+
defaultsOf(MessageReaction, {
|
|
331
|
+
users: (reaction)=>ownedManager(ReactionUserManager.prototype, {
|
|
332
|
+
reaction
|
|
333
|
+
})
|
|
334
|
+
}),
|
|
335
|
+
// The bot's message the component was used on, holding it, in the interaction's channel, as Discord always sends it;
|
|
336
|
+
// the component discord.js finds there by its customId
|
|
337
|
+
defaultsOf(MessageComponentInteraction, {
|
|
338
|
+
message: clickedMessage,
|
|
339
|
+
component: REAL_GETTER
|
|
340
|
+
}),
|
|
341
|
+
// A modal submitted with no fields given has none
|
|
342
|
+
defaultsOf(ModalSubmitInteraction, {
|
|
343
|
+
fields: ()=>createModalFields({}),
|
|
344
|
+
components: ()=>[]
|
|
345
|
+
}),
|
|
346
|
+
defaultsOf(BaseInteraction, {
|
|
347
|
+
applicationId: nextSnowflake,
|
|
348
|
+
token: ()=>'mock-interaction-token',
|
|
349
|
+
version: ()=>1,
|
|
350
|
+
context: ()=>null,
|
|
351
|
+
entitlements: ()=>new Collection(),
|
|
352
|
+
// What Discord sends with an interaction in a server, the member's permissions there; typed for a cached one
|
|
353
|
+
// Each reads where the interaction is as its own, and warns by its own name where strict mocks place it otherwise
|
|
354
|
+
memberPermissions: (interaction)=>readPlaceAs(interaction, 'memberPermissions', ()=>{
|
|
355
|
+
const { guildId } = interaction;
|
|
356
|
+
const member = interaction.member;
|
|
357
|
+
if (!guildId) return null;
|
|
358
|
+
if (member instanceof GuildMember) return inChannel(interaction, member);
|
|
359
|
+
// A raw member's permissions are the string Discord sends, read as discord.js reads them
|
|
360
|
+
return isRawMember(member) && !interaction.guild ? new PermissionsBitField(BigInt(member.permissions)).freeze() : null;
|
|
361
|
+
}),
|
|
362
|
+
// The bot's permissions where the interaction was made: its member's in the channel, or in a thread its parent, as
|
|
363
|
+
// discord.js computes them; none outside a server
|
|
364
|
+
appPermissions: (interaction)=>readPlaceAs(interaction, 'appPermissions', ()=>{
|
|
365
|
+
const { guild } = interaction;
|
|
366
|
+
const me = guild instanceof Guild ? botMemberOf(guild) : null;
|
|
367
|
+
if (!(me instanceof GuildMember) || heldChannelOf(interaction, guild) === null) return noPermissions();
|
|
368
|
+
// discord.js's own computation, given its own overwritesFor over a test's stub on the channel; a channel the
|
|
369
|
+
// mock would make has no overwrites
|
|
370
|
+
const at = overwritingChannel(interaction, guild) ?? {
|
|
371
|
+
guild,
|
|
372
|
+
permissionOverwrites: {
|
|
373
|
+
cache: new Collection()
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const channel = Object.create(at, {
|
|
377
|
+
overwritesFor: {
|
|
378
|
+
value: channelPermissions.overwritesFor
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
const permissions = channelPermissions.memberPermissions.call(channel, me, true);
|
|
382
|
+
// The bot's member has @everyone's too
|
|
383
|
+
warnOnUnsetEveryone(interaction, guild, 'appPermissions', permissions);
|
|
384
|
+
return permissions;
|
|
385
|
+
}, // A server the bot isn't in has no guild in either mode, so no permissions in both
|
|
386
|
+
(place)=>!place.raw)
|
|
387
|
+
}),
|
|
388
|
+
defaultsOf(TextChannel, {
|
|
389
|
+
type: ()=>ChannelType.GuildText,
|
|
390
|
+
nsfw: ()=>false,
|
|
391
|
+
topic: ()=>null,
|
|
392
|
+
rateLimitPerUser: ()=>0
|
|
393
|
+
}),
|
|
394
|
+
defaultsOf(NewsChannel, {
|
|
395
|
+
type: ()=>ChannelType.GuildAnnouncement,
|
|
396
|
+
nsfw: ()=>false,
|
|
397
|
+
topic: ()=>null
|
|
398
|
+
}),
|
|
399
|
+
defaultsOf(VoiceChannel, {
|
|
400
|
+
type: ()=>ChannelType.GuildVoice,
|
|
401
|
+
nsfw: ()=>false
|
|
402
|
+
}),
|
|
403
|
+
defaultsOf(ForumChannel, {
|
|
404
|
+
type: ()=>ChannelType.GuildForum,
|
|
405
|
+
nsfw: ()=>false,
|
|
406
|
+
topic: ()=>null
|
|
407
|
+
}),
|
|
408
|
+
defaultsOf(MediaChannel, {
|
|
409
|
+
type: ()=>ChannelType.GuildMedia,
|
|
410
|
+
nsfw: ()=>false,
|
|
411
|
+
topic: ()=>null
|
|
412
|
+
}),
|
|
413
|
+
defaultsOf(StageChannel, {
|
|
414
|
+
type: ()=>ChannelType.GuildStageVoice,
|
|
415
|
+
nsfw: ()=>false,
|
|
416
|
+
topic: ()=>null
|
|
417
|
+
}),
|
|
418
|
+
defaultsOf(CategoryChannel, {
|
|
419
|
+
type: ()=>ChannelType.GuildCategory
|
|
420
|
+
}),
|
|
421
|
+
defaultsOf(DirectoryChannel, {
|
|
422
|
+
type: ()=>ChannelType.GuildDirectory
|
|
423
|
+
}),
|
|
424
|
+
// A thread's server is its guild's, as discord.js sets it from the thread's guild
|
|
425
|
+
defaultsOf(ThreadChannel, {
|
|
426
|
+
type: ()=>ChannelType.PublicThread,
|
|
427
|
+
name: ()=>'thread',
|
|
428
|
+
guildId: guildIdOf
|
|
429
|
+
}),
|
|
430
|
+
defaultsOf(DMChannel, {
|
|
431
|
+
type: ()=>ChannelType.DM
|
|
432
|
+
}),
|
|
433
|
+
defaultsOf(PartialGroupDMChannel, {
|
|
434
|
+
type: ()=>ChannelType.GroupDM
|
|
435
|
+
}),
|
|
436
|
+
defaultsOf(GuildChannel, {
|
|
437
|
+
name: ()=>'channel',
|
|
438
|
+
guildId: guildIdOf,
|
|
439
|
+
position: ()=>0,
|
|
440
|
+
rawPosition: ()=>0,
|
|
441
|
+
parentId: ()=>null
|
|
442
|
+
}),
|
|
443
|
+
defaultsOf(BaseChannel, {
|
|
444
|
+
createdTimestamp: REAL_GETTER,
|
|
445
|
+
createdAt: REAL_GETTER
|
|
446
|
+
}),
|
|
447
|
+
defaultsOf(Client, {
|
|
448
|
+
readyTimestamp: (client)=>stampedAt(client, 'Client.readyTimestamp'),
|
|
449
|
+
readyAt: REAL_GETTER,
|
|
450
|
+
uptime: REAL_GETTER,
|
|
451
|
+
token: ()=>null
|
|
452
|
+
}),
|
|
453
|
+
// What a member's roles show, computed from the roles in the cache as discord.js does
|
|
454
|
+
defaultsOf(GuildMemberRoleManager, {
|
|
455
|
+
color: REAL_GETTER,
|
|
456
|
+
hoist: REAL_GETTER,
|
|
457
|
+
icon: REAL_GETTER
|
|
458
|
+
}),
|
|
459
|
+
defaultsOf(BaseGuild, {
|
|
460
|
+
nameAcronym: REAL_GETTER
|
|
461
|
+
})
|
|
462
|
+
]);
|
|
463
|
+
/**
|
|
464
|
+
* What strict mocks also read as Discord sends it, for discord.js's getters that read it: a server for a channel or
|
|
465
|
+
* thread made without one, a thread's parent text channel in it, and data that is otherwise a placeholder.
|
|
466
|
+
*/ const STRICT_DEFAULTS = new Map([
|
|
467
|
+
defaultsOf(Message, {
|
|
468
|
+
content: ()=>'',
|
|
469
|
+
editedTimestamp: ()=>null,
|
|
470
|
+
editedAt: REAL_GETTER
|
|
471
|
+
}),
|
|
472
|
+
defaultsOf(MessageMentions, {
|
|
473
|
+
repliedUser: ()=>null
|
|
474
|
+
}),
|
|
475
|
+
defaultsOf(Guild, {
|
|
476
|
+
rulesChannelId: ()=>null,
|
|
477
|
+
publicUpdatesChannelId: ()=>null,
|
|
478
|
+
systemChannel: REAL_GETTER
|
|
479
|
+
}),
|
|
480
|
+
defaultsOf(BaseGuild, {
|
|
481
|
+
verified: REAL_GETTER
|
|
482
|
+
}),
|
|
483
|
+
defaultsOf(GuildMember, {
|
|
484
|
+
presence: REAL_GETTER
|
|
485
|
+
}),
|
|
486
|
+
defaultsOf(ModalSubmitInteraction, {
|
|
487
|
+
message: ()=>null
|
|
488
|
+
}),
|
|
489
|
+
defaultsOf(ThreadChannel, {
|
|
490
|
+
guild: guildFromOwnId,
|
|
491
|
+
guildId: (thread)=>thread.guild.id,
|
|
492
|
+
parentId: (thread)=>{
|
|
493
|
+
const parent = createMockChannel(TextChannel, {
|
|
494
|
+
guild: thread.guild
|
|
495
|
+
});
|
|
496
|
+
thread.guild.channels.cache.set(parent.id, parent);
|
|
497
|
+
madeParentIds.set(thread, parent.id);
|
|
498
|
+
return parent.id;
|
|
499
|
+
},
|
|
500
|
+
parent: REAL_GETTER,
|
|
501
|
+
ownerId: nextSnowflake,
|
|
502
|
+
archived: ()=>false,
|
|
503
|
+
locked: ()=>false
|
|
504
|
+
}),
|
|
505
|
+
defaultsOf(GuildChannel, {
|
|
506
|
+
guild: guildFromOwnId,
|
|
507
|
+
parent: REAL_GETTER
|
|
508
|
+
}),
|
|
509
|
+
// Full once its members reach a user limit, which a voice channel has none of unless given one
|
|
510
|
+
defaultsOf(BaseGuildVoiceChannel, {
|
|
511
|
+
userLimit: ()=>0,
|
|
512
|
+
full: REAL_GETTER
|
|
513
|
+
}),
|
|
514
|
+
// A reaction's message, as discord.js always has one, is a whole one
|
|
515
|
+
defaultsOf(MessageReaction, {
|
|
516
|
+
count: ()=>1,
|
|
517
|
+
me: ()=>false,
|
|
518
|
+
message: ()=>createMockMessage()
|
|
519
|
+
})
|
|
520
|
+
]);
|
|
521
|
+
return (target, key)=>{
|
|
522
|
+
for (const defaults of strictMocks() ? [
|
|
523
|
+
STRICT_DEFAULTS,
|
|
524
|
+
DEFAULTS
|
|
525
|
+
] : [
|
|
526
|
+
DEFAULTS
|
|
527
|
+
]){
|
|
528
|
+
for(let proto = Object.getPrototypeOf(target); proto !== null; proto = Object.getPrototypeOf(proto)){
|
|
529
|
+
const found = defaults.get(proto)?.[key];
|
|
530
|
+
if (found !== undefined) return found;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return undefined;
|
|
534
|
+
};
|
|
441
535
|
}
|
|
442
536
|
|
|
443
|
-
export { REAL_GETTER,
|
|
537
|
+
export { REAL_GETTER, discordDefaults, placeholderValue };
|