@nubisjs/core 0.1.0-next.0 → 0.1.0-next.1
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/command.d.ts +2 -1
- package/dist/component-dispatch.d.ts +2 -2
- package/dist/dispatch.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +157 -93
- package/dist/localization.d.ts +34 -0
- package/dist/menu.d.ts +4 -1
- package/dist/modal.d.ts +1 -1
- package/dist/option.d.ts +4 -1
- package/dist/reply.d.ts +1 -1
- package/dist/tree.d.ts +2 -1
- package/package.json +1 -1
package/dist/command.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { APIChatInputApplicationCommandInteraction } from "discord-api-type
|
|
|
2
2
|
import type { NubisApi } from "./api";
|
|
3
3
|
import type { ModalData } from "./component";
|
|
4
4
|
import type { Guard } from "./guard";
|
|
5
|
+
import type { Localizable } from "./localization";
|
|
5
6
|
import type { Middleware } from "./middleware";
|
|
6
7
|
import type { OptionMap, ResolvedOptions } from "./option";
|
|
7
8
|
import type { ReplyInput } from "./reply";
|
|
@@ -36,7 +37,7 @@ export interface CommandContext<T extends OptionMap> {
|
|
|
36
37
|
/** Opens a modal. Must be the first response, so call it before reply(). */
|
|
37
38
|
showModal(modal: ModalData): Promise<void>;
|
|
38
39
|
}
|
|
39
|
-
export interface CommandConfig<T extends OptionMap> {
|
|
40
|
+
export interface CommandConfig<T extends OptionMap> extends Localizable {
|
|
40
41
|
readonly description: string;
|
|
41
42
|
readonly options?: T;
|
|
42
43
|
/** Makes replies (and the automatic "thinking…") visible only to the user. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { APIMessageComponentInteraction, APIModalSubmitInteraction } from "discord-api-types/v10";
|
|
2
|
-
import { type ComponentDefinition, type ComponentRuntime } from "./component";
|
|
3
2
|
import type { NubisApi } from "./api";
|
|
3
|
+
import { type ComponentDefinition, type ComponentRuntime } from "./component";
|
|
4
4
|
import { type ErrorHandler } from "./errors";
|
|
5
5
|
import { type Guard } from "./guard";
|
|
6
|
-
import { type Middleware } from "./middleware";
|
|
7
6
|
import { type Scheduler, type Transport } from "./lifecycle";
|
|
7
|
+
import { type Middleware } from "./middleware";
|
|
8
8
|
export type ComponentRegistry = ReadonlyMap<string, ComponentRuntime>;
|
|
9
9
|
export declare function createComponentRegistry(components: readonly ComponentDefinition[]): ComponentRegistry;
|
|
10
10
|
export type ComponentDispatchResult = {
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { type APIApplicationCommandInteractionDataBasicOption, type APIApplicati
|
|
|
2
2
|
import type { NubisApi } from "./api";
|
|
3
3
|
import { type ErrorHandler } from "./errors";
|
|
4
4
|
import { type Guard } from "./guard";
|
|
5
|
-
import { type Middleware } from "./middleware";
|
|
6
5
|
import { type Lifecycle, type Scheduler, type Transport } from "./lifecycle";
|
|
6
|
+
import { type Middleware } from "./middleware";
|
|
7
7
|
import type { CommandLeaf, CommandParent, CommandTree } from "./tree";
|
|
8
8
|
export interface DispatchOptions {
|
|
9
9
|
/** See LifecycleOptions.autoDeferMs. */
|
package/dist/index.d.ts
CHANGED
|
@@ -2,28 +2,26 @@ export type { NubisApi } from "./api";
|
|
|
2
2
|
export { missingApi } from "./api";
|
|
3
3
|
export type { AutocompleteOptions, AutocompleteResult } from "./autocomplete";
|
|
4
4
|
export { dispatchAutocomplete } from "./autocomplete";
|
|
5
|
-
export type { CommandConfig,
|
|
5
|
+
export type { CommandConfig, CommandContext, CommandContextName, CommandDefinition, IntegrationTypeName, TopLevelSettings, } from "./command";
|
|
6
6
|
export { command } from "./command";
|
|
7
7
|
export type { ButtonConfig, ButtonDefinition, ButtonOverrides, ButtonStyleName, ChannelSelectConfig, ChannelSelection, ChannelSelectOverrides, ComponentContext, ComponentDefinition, ComponentKind, ComponentState, EmojiInput, EntitySelectDefinition, LinkButtonConfig, MentionableSelectConfig, MentionableSelection, MentionableSelectOverrides, ModalData, RoleSelectConfig, RoleSelection, RoleSelectOverrides, SelectOption, StringSelectConfig, StringSelectDefinition, StringSelectOverrides, UserSelectConfig, UserSelection, UserSelectOverrides, } from "./component";
|
|
8
8
|
export { button, channelSelect, linkButton, mentionableSelect, roleSelect, stringSelect, toEmoji, userSelect, } from "./component";
|
|
9
|
-
export type { MemoryStoreOptions, StateStore } from "./store";
|
|
10
|
-
export { memoryStore } from "./store";
|
|
11
9
|
export type { ComponentDispatchOptions, ComponentDispatchResult, ComponentRegistry, } from "./component-dispatch";
|
|
12
10
|
export { createComponentRegistry, dispatchComponent, dispatchModal, STALE_MESSAGE, } from "./component-dispatch";
|
|
13
11
|
export type { DispatchOptions, DispatchResult } from "./dispatch";
|
|
14
12
|
export { dispatch, ERROR_MESSAGE, UNKNOWN_COMMAND_MESSAGE } from "./dispatch";
|
|
15
|
-
export type { InteractionKind, Lifecycle, LifecycleOptions, Scheduler, Transport, } from "./lifecycle";
|
|
16
|
-
export { createLifecycle, DEFAULT_AUTO_DEFER_MS } from "./lifecycle";
|
|
17
13
|
export type { ErrorHandler, ErrorInfo } from "./errors";
|
|
18
14
|
export { NubisError } from "./errors";
|
|
19
15
|
export type { AnyEventDefinition, CamelCase, EventContext, EventDefinition, EventDispatchResult, EventName, EventPayloads, EventRegistry, } from "./events";
|
|
20
16
|
export { createEventRegistry, dispatchEvent, event, toEventName, } from "./events";
|
|
21
17
|
export type { CooldownOptions, Denial, Guard, GuardContext, GuardedInteraction, GuardResult, } from "./guard";
|
|
22
18
|
export { botHasPermission, cooldown, guard, guildOnly, hasPermission, hasRole, invokerOnly, } from "./guard";
|
|
23
|
-
export type {
|
|
24
|
-
export {
|
|
19
|
+
export type { InteractionKind, Lifecycle, LifecycleOptions, Scheduler, Transport, } from "./lifecycle";
|
|
20
|
+
export { createLifecycle, DEFAULT_AUTO_DEFER_MS } from "./lifecycle";
|
|
25
21
|
export type { MenuDefinition, MenuDispatchResult, MenuPayload, MessageMenuConfig, MessageMenuContext, UserMenuConfig, UserMenuContext, } from "./menu";
|
|
26
22
|
export { dispatchMenu, messageCommand, toMenuPayloads, userCommand, } from "./menu";
|
|
23
|
+
export type { Middleware, MiddlewareContext } from "./middleware";
|
|
24
|
+
export { middleware } from "./middleware";
|
|
27
25
|
export type { FieldMap, ModalConfig, ModalContext, ModalDefinition, ModalOverrides, ModalValues, TextInputConfig, TextInputDefinition, } from "./modal";
|
|
28
26
|
export { modal, textInput } from "./modal";
|
|
29
27
|
export type { AnyOptionConfig, AutocompleteContext, AutocompleteHandler, BasicOptionConfig, ChannelOptionConfig, Choice, IsRequired, NumberOptionConfig, OptionDefinition, OptionKind, OptionMap, OptionValue, OptionValues, ResolvedOptions, StringOptionConfig, ValueOf, } from "./option";
|
|
@@ -34,8 +32,10 @@ export type { SchemaInput, SchemaOutput, StandardResult, StandardSchemaV1, } fro
|
|
|
34
32
|
export { stateOf } from "./schema";
|
|
35
33
|
export type { CommandPayload } from "./serialize";
|
|
36
34
|
export { toCommandPayloads } from "./serialize";
|
|
35
|
+
export type { DiscordLocale, Localizable, Localizations } from "./localization";
|
|
36
|
+
export type { MemoryStoreOptions, StateStore } from "./store";
|
|
37
|
+
export { memoryStore } from "./store";
|
|
37
38
|
export type { AnyCommandPayload, SyncPlan } from "./sync";
|
|
38
39
|
export { planSync } from "./sync";
|
|
39
|
-
export type { CommandFile, CommandFolder, CommandLeaf, CommandNode, CommandParent, CommandTree, } from "./tree";
|
|
40
|
-
export type { FolderSettings } from "./tree";
|
|
40
|
+
export type { CommandFile, CommandFolder, CommandLeaf, CommandNode, CommandParent, CommandTree, FolderSettings, } from "./tree";
|
|
41
41
|
export { buildCommandTree, folder } from "./tree";
|
package/dist/index.js
CHANGED
|
@@ -114,94 +114,11 @@ function cooldown(options) {
|
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// src/middleware.ts
|
|
118
|
-
function middleware(run) {
|
|
119
|
-
return run;
|
|
120
|
-
}
|
|
121
|
-
function runWithMiddleware(stack, context, handler) {
|
|
122
|
-
const call = (index) => {
|
|
123
|
-
const current = stack[index];
|
|
124
|
-
if (!current)
|
|
125
|
-
return handler();
|
|
126
|
-
let called = false;
|
|
127
|
-
return current(context, () => {
|
|
128
|
-
if (called)
|
|
129
|
-
throw new Error("A middleware called next() twice.");
|
|
130
|
-
called = true;
|
|
131
|
-
return call(index + 1);
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
return call(0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
117
|
// src/lifecycle.ts
|
|
138
118
|
import {
|
|
139
119
|
InteractionResponseType
|
|
140
120
|
} from "discord-api-types/v10";
|
|
141
121
|
|
|
142
|
-
// src/store.ts
|
|
143
|
-
function memoryStore(options = {}) {
|
|
144
|
-
const {
|
|
145
|
-
ttlMs = 24 * 60 * 60 * 1000,
|
|
146
|
-
maxEntries = 1e4,
|
|
147
|
-
now = Date.now
|
|
148
|
-
} = options;
|
|
149
|
-
const entries = new Map;
|
|
150
|
-
return {
|
|
151
|
-
get size() {
|
|
152
|
-
return entries.size;
|
|
153
|
-
},
|
|
154
|
-
set(key, value) {
|
|
155
|
-
entries.set(key, { value, expires: now() + ttlMs });
|
|
156
|
-
while (entries.size > maxEntries) {
|
|
157
|
-
const oldest = entries.keys().next().value;
|
|
158
|
-
if (oldest === undefined)
|
|
159
|
-
break;
|
|
160
|
-
entries.delete(oldest);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
get(key) {
|
|
164
|
-
const entry = entries.get(key);
|
|
165
|
-
if (!entry)
|
|
166
|
-
return;
|
|
167
|
-
if (entry.expires <= now()) {
|
|
168
|
-
entries.delete(key);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
return entry.value;
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
176
|
-
function newStateKey() {
|
|
177
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
178
|
-
let key = "";
|
|
179
|
-
for (const byte of bytes)
|
|
180
|
-
key += ALPHABET[byte % 64];
|
|
181
|
-
return key;
|
|
182
|
-
}
|
|
183
|
-
var pending = new Set;
|
|
184
|
-
var failures = [];
|
|
185
|
-
function trackStateWrite(write) {
|
|
186
|
-
if (!(write instanceof Promise))
|
|
187
|
-
return;
|
|
188
|
-
const tracked = write.then(() => {
|
|
189
|
-
pending.delete(tracked);
|
|
190
|
-
}, (error) => {
|
|
191
|
-
pending.delete(tracked);
|
|
192
|
-
failures.push(error);
|
|
193
|
-
});
|
|
194
|
-
pending.add(tracked);
|
|
195
|
-
}
|
|
196
|
-
async function settleStateWrites() {
|
|
197
|
-
if (pending.size > 0)
|
|
198
|
-
await Promise.all([...pending]);
|
|
199
|
-
if (failures.length > 0) {
|
|
200
|
-
const [first] = failures.splice(0);
|
|
201
|
-
throw new NubisError(`Saving component state failed: ${String(first)}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
122
|
// src/reply.ts
|
|
206
123
|
import {
|
|
207
124
|
AllowedMentionsTypes,
|
|
@@ -271,6 +188,69 @@ function toReplyMessage(input) {
|
|
|
271
188
|
};
|
|
272
189
|
}
|
|
273
190
|
|
|
191
|
+
// src/store.ts
|
|
192
|
+
function memoryStore(options = {}) {
|
|
193
|
+
const {
|
|
194
|
+
ttlMs = 24 * 60 * 60 * 1000,
|
|
195
|
+
maxEntries = 1e4,
|
|
196
|
+
now = Date.now
|
|
197
|
+
} = options;
|
|
198
|
+
const entries = new Map;
|
|
199
|
+
return {
|
|
200
|
+
get size() {
|
|
201
|
+
return entries.size;
|
|
202
|
+
},
|
|
203
|
+
set(key, value) {
|
|
204
|
+
entries.set(key, { value, expires: now() + ttlMs });
|
|
205
|
+
while (entries.size > maxEntries) {
|
|
206
|
+
const oldest = entries.keys().next().value;
|
|
207
|
+
if (oldest === undefined)
|
|
208
|
+
break;
|
|
209
|
+
entries.delete(oldest);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
get(key) {
|
|
213
|
+
const entry = entries.get(key);
|
|
214
|
+
if (!entry)
|
|
215
|
+
return;
|
|
216
|
+
if (entry.expires <= now()) {
|
|
217
|
+
entries.delete(key);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
return entry.value;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
225
|
+
function newStateKey() {
|
|
226
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
227
|
+
let key = "";
|
|
228
|
+
for (const byte of bytes)
|
|
229
|
+
key += ALPHABET[byte % 64];
|
|
230
|
+
return key;
|
|
231
|
+
}
|
|
232
|
+
var pending = new Set;
|
|
233
|
+
var failures = [];
|
|
234
|
+
function trackStateWrite(write) {
|
|
235
|
+
if (!(write instanceof Promise))
|
|
236
|
+
return;
|
|
237
|
+
const tracked = write.then(() => {
|
|
238
|
+
pending.delete(tracked);
|
|
239
|
+
}, (error) => {
|
|
240
|
+
pending.delete(tracked);
|
|
241
|
+
failures.push(error);
|
|
242
|
+
});
|
|
243
|
+
pending.add(tracked);
|
|
244
|
+
}
|
|
245
|
+
async function settleStateWrites() {
|
|
246
|
+
if (pending.size > 0)
|
|
247
|
+
await Promise.all([...pending]);
|
|
248
|
+
if (failures.length > 0) {
|
|
249
|
+
const [first] = failures.splice(0);
|
|
250
|
+
throw new NubisError(`Saving component state failed: ${String(first)}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
274
254
|
// src/lifecycle.ts
|
|
275
255
|
var defaultScheduler = (run, ms) => {
|
|
276
256
|
const timer = setTimeout(run, ms);
|
|
@@ -409,6 +389,26 @@ function createLifecycle(transport, options) {
|
|
|
409
389
|
};
|
|
410
390
|
}
|
|
411
391
|
|
|
392
|
+
// src/middleware.ts
|
|
393
|
+
function middleware(run) {
|
|
394
|
+
return run;
|
|
395
|
+
}
|
|
396
|
+
function runWithMiddleware(stack, context, handler) {
|
|
397
|
+
const call = (index) => {
|
|
398
|
+
const current = stack[index];
|
|
399
|
+
if (!current)
|
|
400
|
+
return handler();
|
|
401
|
+
let called = false;
|
|
402
|
+
return current(context, () => {
|
|
403
|
+
if (called)
|
|
404
|
+
throw new Error("A middleware called next() twice.");
|
|
405
|
+
called = true;
|
|
406
|
+
return call(index + 1);
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
return call(0);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
412
|
// src/dispatch.ts
|
|
413
413
|
var ERROR_MESSAGE = "Something went wrong. Please try again.";
|
|
414
414
|
var UNKNOWN_COMMAND_MESSAGE = "This command isn't available anymore. Discord may still be catching up.";
|
|
@@ -1046,13 +1046,8 @@ import {
|
|
|
1046
1046
|
ApplicationCommandType as ApplicationCommandType2
|
|
1047
1047
|
} from "discord-api-types/v10";
|
|
1048
1048
|
|
|
1049
|
-
// src/
|
|
1050
|
-
import {
|
|
1051
|
-
ApplicationCommandOptionType as ApplicationCommandOptionType2,
|
|
1052
|
-
ApplicationCommandType,
|
|
1053
|
-
ApplicationIntegrationType,
|
|
1054
|
-
InteractionContextType
|
|
1055
|
-
} from "discord-api-types/v10";
|
|
1049
|
+
// src/localization.ts
|
|
1050
|
+
import { Locale } from "discord-api-types/v10";
|
|
1056
1051
|
|
|
1057
1052
|
// src/names.ts
|
|
1058
1053
|
var NAME = /^[-_'\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
|
|
@@ -1067,7 +1062,53 @@ function assertValidDescription(description, where) {
|
|
|
1067
1062
|
}
|
|
1068
1063
|
}
|
|
1069
1064
|
|
|
1065
|
+
// src/localization.ts
|
|
1066
|
+
var LOCALES = new Set(Object.values(Locale));
|
|
1067
|
+
function checkLocales(map, where) {
|
|
1068
|
+
for (const locale of Object.keys(map)) {
|
|
1069
|
+
if (!LOCALES.has(locale)) {
|
|
1070
|
+
throw new NubisError(`${where}: "${locale}" isn't a Discord locale. Use codes like "tr", "de", "en-US" or "pt-BR".`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
var entries = (map) => Object.entries(map);
|
|
1075
|
+
function nameLocalizations(map, where) {
|
|
1076
|
+
if (!map)
|
|
1077
|
+
return {};
|
|
1078
|
+
checkLocales(map, where);
|
|
1079
|
+
for (const [locale, name] of entries(map)) {
|
|
1080
|
+
assertValidName(name, `${where} (${locale})`);
|
|
1081
|
+
}
|
|
1082
|
+
return { name_localizations: map };
|
|
1083
|
+
}
|
|
1084
|
+
function descriptionLocalizations(map, where) {
|
|
1085
|
+
if (!map)
|
|
1086
|
+
return {};
|
|
1087
|
+
checkLocales(map, where);
|
|
1088
|
+
for (const [locale, description] of entries(map)) {
|
|
1089
|
+
assertValidDescription(description, `${where} (${locale})`);
|
|
1090
|
+
}
|
|
1091
|
+
return { description_localizations: map };
|
|
1092
|
+
}
|
|
1093
|
+
function labelLocalizations(map, where, max) {
|
|
1094
|
+
if (!map)
|
|
1095
|
+
return {};
|
|
1096
|
+
checkLocales(map, where);
|
|
1097
|
+
for (const [locale, label] of entries(map)) {
|
|
1098
|
+
if (label.trim().length === 0 || label.length > max) {
|
|
1099
|
+
throw new NubisError(`${where} (${locale}): must be 1-${max} characters and not blank (got ${label.length}).`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return { name_localizations: map };
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1070
1105
|
// src/serialize.ts
|
|
1106
|
+
import {
|
|
1107
|
+
ApplicationCommandOptionType as ApplicationCommandOptionType2,
|
|
1108
|
+
ApplicationCommandType,
|
|
1109
|
+
ApplicationIntegrationType,
|
|
1110
|
+
InteractionContextType
|
|
1111
|
+
} from "discord-api-types/v10";
|
|
1071
1112
|
var OPTION_TYPES = {
|
|
1072
1113
|
string: ApplicationCommandOptionType2.String,
|
|
1073
1114
|
integer: ApplicationCommandOptionType2.Integer,
|
|
@@ -1114,9 +1155,10 @@ function optionDetails(name, option, where) {
|
|
|
1114
1155
|
throw new NubisError(`${at}: integer choices must be whole numbers.`);
|
|
1115
1156
|
}
|
|
1116
1157
|
}
|
|
1117
|
-
details.choices = choices.map(({ name: label, value }) => ({
|
|
1158
|
+
details.choices = choices.map(({ name: label, value, nameLocalizations: labels }) => ({
|
|
1118
1159
|
name: label,
|
|
1119
|
-
value
|
|
1160
|
+
value,
|
|
1161
|
+
...labelLocalizations(labels, `${at} choice "${label}"`, MAX_CHOICE_NAME)
|
|
1120
1162
|
}));
|
|
1121
1163
|
}
|
|
1122
1164
|
if (autocomplete)
|
|
@@ -1158,6 +1200,7 @@ function serializeOption(name, option, where) {
|
|
|
1158
1200
|
type: OPTION_TYPES[option.kind],
|
|
1159
1201
|
name,
|
|
1160
1202
|
description,
|
|
1203
|
+
...localized(option.config, `${where} option "${name}"`),
|
|
1161
1204
|
required: option.required,
|
|
1162
1205
|
...optionDetails(name, option, where)
|
|
1163
1206
|
};
|
|
@@ -1181,6 +1224,7 @@ function serializeChild(node) {
|
|
|
1181
1224
|
type: ApplicationCommandOptionType2.Subcommand,
|
|
1182
1225
|
name: node.name,
|
|
1183
1226
|
description: node.definition.description,
|
|
1227
|
+
...localized(node.definition, slash(node.path)),
|
|
1184
1228
|
options: serializeLeafOptions(node)
|
|
1185
1229
|
};
|
|
1186
1230
|
}
|
|
@@ -1188,6 +1232,7 @@ function serializeChild(node) {
|
|
|
1188
1232
|
type: ApplicationCommandOptionType2.SubcommandGroup,
|
|
1189
1233
|
name: node.name,
|
|
1190
1234
|
description: folderDescription(node),
|
|
1235
|
+
...localized(node.folder ?? {}, `commands/${node.path.join("/")}`),
|
|
1191
1236
|
options: [...node.children.values()].map((child) => {
|
|
1192
1237
|
if (child.type !== "command") {
|
|
1193
1238
|
throw new NubisError(`${slash(child.path)}: subcommand groups can't contain other groups.`);
|
|
@@ -1196,6 +1241,10 @@ function serializeChild(node) {
|
|
|
1196
1241
|
})
|
|
1197
1242
|
};
|
|
1198
1243
|
}
|
|
1244
|
+
var localized = (settings, where) => ({
|
|
1245
|
+
...nameLocalizations(settings.nameLocalizations, where),
|
|
1246
|
+
...descriptionLocalizations(settings.descriptionLocalizations, where)
|
|
1247
|
+
});
|
|
1199
1248
|
var CONTEXTS = {
|
|
1200
1249
|
guild: InteractionContextType.Guild,
|
|
1201
1250
|
"bot-dm": InteractionContextType.BotDM,
|
|
@@ -1250,6 +1299,7 @@ function toCommandPayloads(tree) {
|
|
|
1250
1299
|
type: ApplicationCommandType.ChatInput,
|
|
1251
1300
|
name: node.name,
|
|
1252
1301
|
description: node.definition.description,
|
|
1302
|
+
...localized(node.definition, slash(node.path)),
|
|
1253
1303
|
options: serializeLeafOptions(node),
|
|
1254
1304
|
...topLevelFields(node.definition)
|
|
1255
1305
|
};
|
|
@@ -1258,6 +1308,7 @@ function toCommandPayloads(tree) {
|
|
|
1258
1308
|
type: ApplicationCommandType.ChatInput,
|
|
1259
1309
|
name: node.name,
|
|
1260
1310
|
description: folderDescription(node),
|
|
1311
|
+
...localized(node.folder ?? {}, `commands/${node.path.join("/")}`),
|
|
1261
1312
|
options: [...node.children.values()].map(serializeChild),
|
|
1262
1313
|
...topLevelFields(node.folder ?? {})
|
|
1263
1314
|
};
|
|
@@ -1297,6 +1348,7 @@ function toMenuPayloads(menus) {
|
|
|
1297
1348
|
return menus.map((menu) => ({
|
|
1298
1349
|
type: TYPES[menu.kind],
|
|
1299
1350
|
name: menu.name,
|
|
1351
|
+
...labelLocalizations(menu.config.nameLocalizations, `Context menu "${menu.name}"`, MAX_NAME2),
|
|
1300
1352
|
...topLevelFields(menu.config)
|
|
1301
1353
|
}));
|
|
1302
1354
|
}
|
|
@@ -1542,13 +1594,23 @@ import {
|
|
|
1542
1594
|
ApplicationCommandType as ApplicationCommandType3
|
|
1543
1595
|
} from "discord-api-types/v10";
|
|
1544
1596
|
var sorted = (values) => values ? [...values].sort((a, b) => a - b) : null;
|
|
1597
|
+
function localizations(map) {
|
|
1598
|
+
const entries = Object.entries(map ?? {}).filter((entry) => typeof entry[1] === "string");
|
|
1599
|
+
return entries.length === 0 ? null : Object.fromEntries(entries.sort(([a], [b]) => a.localeCompare(b)));
|
|
1600
|
+
}
|
|
1545
1601
|
function normalizeOption(option) {
|
|
1546
1602
|
return {
|
|
1547
1603
|
type: option.type,
|
|
1548
1604
|
name: option.name,
|
|
1605
|
+
name_localizations: localizations(option.name_localizations),
|
|
1549
1606
|
description: option.description ?? "",
|
|
1607
|
+
description_localizations: localizations(option.description_localizations),
|
|
1550
1608
|
required: option.required ?? false,
|
|
1551
|
-
choices: option.choices?.map(({ name, value }) => ({
|
|
1609
|
+
choices: option.choices?.map(({ name, name_localizations, value }) => ({
|
|
1610
|
+
name,
|
|
1611
|
+
name_localizations: localizations(name_localizations),
|
|
1612
|
+
value
|
|
1613
|
+
})) ?? null,
|
|
1552
1614
|
autocomplete: option.autocomplete ?? false,
|
|
1553
1615
|
min_value: option.min_value ?? null,
|
|
1554
1616
|
max_value: option.max_value ?? null,
|
|
@@ -1563,7 +1625,9 @@ function normalize(command) {
|
|
|
1563
1625
|
return {
|
|
1564
1626
|
type: command.type ?? ApplicationCommandType3.ChatInput,
|
|
1565
1627
|
name: command.name,
|
|
1628
|
+
name_localizations: localizations(command.name_localizations),
|
|
1566
1629
|
description: command.description ?? "",
|
|
1630
|
+
description_localizations: localizations(command.description_localizations),
|
|
1567
1631
|
default_member_permissions: command.default_member_permissions ?? null,
|
|
1568
1632
|
contexts: sorted(command.contexts),
|
|
1569
1633
|
integration_types: integrationTypes === null || integrationTypes.length === 1 && integrationTypes[0] === 0 ? null : integrationTypes,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Locale } from "discord-api-types/v10";
|
|
2
|
+
/** One of Discord's locales, as a string: "tr", "en-US", "pt-BR"… */
|
|
3
|
+
export type DiscordLocale = `${Locale}`;
|
|
4
|
+
/** Text in other languages, by Discord locale: `{ tr: "Bir zar at", de: "Würfle" }`. */
|
|
5
|
+
export type Localizations = Readonly<Partial<Record<DiscordLocale, string>>>;
|
|
6
|
+
/**
|
|
7
|
+
* What Discord's command picker shows in other languages. Discord picks the
|
|
8
|
+
* entry for the user's client language, and the plain `name`/`description`
|
|
9
|
+
* everywhere else.
|
|
10
|
+
*/
|
|
11
|
+
export interface Localizable {
|
|
12
|
+
/**
|
|
13
|
+
* The name in other languages. Follows the same rules as the name:
|
|
14
|
+
* 1-32 lowercase letters, digits, "-" or "_".
|
|
15
|
+
*/
|
|
16
|
+
readonly nameLocalizations?: Localizations;
|
|
17
|
+
/** The description in other languages, 1-100 characters each. */
|
|
18
|
+
readonly descriptionLocalizations?: Localizations;
|
|
19
|
+
}
|
|
20
|
+
/** `name_localizations` for a slash command, option or folder, checked. */
|
|
21
|
+
export declare function nameLocalizations(map: Localizations | undefined, where: string): {
|
|
22
|
+
name_localizations?: Localizations;
|
|
23
|
+
};
|
|
24
|
+
/** `description_localizations`, checked. */
|
|
25
|
+
export declare function descriptionLocalizations(map: Localizations | undefined, where: string): {
|
|
26
|
+
description_localizations?: Localizations;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* `name_localizations` for something whose name is free text: a choice
|
|
30
|
+
* (1-100 characters) or a context menu command (1-32).
|
|
31
|
+
*/
|
|
32
|
+
export declare function labelLocalizations(map: Localizations | undefined, where: string, max: number): {
|
|
33
|
+
name_localizations?: Localizations;
|
|
34
|
+
};
|
package/dist/menu.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import type { TopLevelSettings } from "./command";
|
|
|
4
4
|
import type { ModalData } from "./component";
|
|
5
5
|
import { type DispatchOptions } from "./dispatch";
|
|
6
6
|
import { type Guard } from "./guard";
|
|
7
|
+
import type { Transport } from "./lifecycle";
|
|
8
|
+
import { type Localizations } from "./localization";
|
|
7
9
|
import { type Middleware } from "./middleware";
|
|
8
10
|
import type { ReplyInput } from "./reply";
|
|
9
|
-
import type { Transport } from "./lifecycle";
|
|
10
11
|
interface MenuContextBase {
|
|
11
12
|
/** Discord's API: act on the user or message. */
|
|
12
13
|
readonly api: NubisApi;
|
|
@@ -32,6 +33,8 @@ export interface MessageMenuContext extends MenuContextBase {
|
|
|
32
33
|
interface MenuConfigBase extends TopLevelSettings {
|
|
33
34
|
/** Shown in the right-click menu: 1-32 characters, spaces and capitals allowed ("Report message"). */
|
|
34
35
|
readonly name: string;
|
|
36
|
+
/** The name in other languages, by Discord locale; free text like `name`. */
|
|
37
|
+
readonly nameLocalizations?: Localizations;
|
|
35
38
|
readonly ephemeral?: boolean;
|
|
36
39
|
readonly guards?: readonly Guard[];
|
|
37
40
|
readonly middleware?: readonly Middleware[];
|
package/dist/modal.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type APIModalSubmitInteraction } from "discord-api-types/v10";
|
|
2
|
-
import { type AnySchema, type ComponentState, type ModalData, type RenderArgs, type WithRuntime } from "./component";
|
|
3
2
|
import type { NubisApi } from "./api";
|
|
3
|
+
import { type AnySchema, type ComponentState, type ModalData, type RenderArgs, type WithRuntime } from "./component";
|
|
4
4
|
import type { Guard } from "./guard";
|
|
5
5
|
import type { Middleware } from "./middleware";
|
|
6
6
|
import type { IsRequired } from "./option";
|
package/dist/option.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { APIApplicationCommandAutocompleteInteraction, APIInteractionDataResolvedChannel, APIRole, APIUser, ChannelType } from "discord-api-types/v10";
|
|
2
2
|
import type { NubisApi } from "./api";
|
|
3
|
+
import type { Localizable, Localizations } from "./localization";
|
|
3
4
|
/** What each option kind resolves to inside `run()`. */
|
|
4
5
|
export interface OptionValues {
|
|
5
6
|
string: string;
|
|
@@ -15,6 +16,8 @@ export type OptionKind = keyof OptionValues;
|
|
|
15
16
|
export interface Choice<V extends string | number = string | number> {
|
|
16
17
|
readonly name: string;
|
|
17
18
|
readonly value: V;
|
|
19
|
+
/** The choice's name in other languages, by Discord locale. */
|
|
20
|
+
readonly nameLocalizations?: Localizations;
|
|
18
21
|
}
|
|
19
22
|
export interface AutocompleteContext {
|
|
20
23
|
/** What the user has typed into this option so far. Always a string while typing. */
|
|
@@ -26,7 +29,7 @@ export interface AutocompleteContext {
|
|
|
26
29
|
}
|
|
27
30
|
/** Suggests values while the user types. Up to 25 are shown; Nubis drops the rest. */
|
|
28
31
|
export type AutocompleteHandler<V extends string | number> = (context: AutocompleteContext) => readonly Choice<V>[] | Promise<readonly Choice<V>[]>;
|
|
29
|
-
interface BaseOptionConfig {
|
|
32
|
+
interface BaseOptionConfig extends Localizable {
|
|
30
33
|
readonly required?: boolean;
|
|
31
34
|
/** Shown in Discord's command picker. Defaults to the option name. */
|
|
32
35
|
readonly description?: string;
|
package/dist/reply.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type APIActionRowComponent, type APIAllowedMentions, type
|
|
1
|
+
import { type APIActionRowComponent, type APIAllowedMentions, type APIButtonComponent, type APIComponentInMessageActionRow, type APIEmbed, type APIInteractionResponseCallbackData, type APISelectMenuComponent } from "discord-api-types/v10";
|
|
2
2
|
export type ActionRow = APIActionRowComponent<APIComponentInMessageActionRow>;
|
|
3
3
|
/** A button or select menu, or a row you've laid out yourself. */
|
|
4
4
|
export type ComponentInput = APIButtonComponent | APISelectMenuComponent | ActionRow;
|
package/dist/tree.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CommandDefinition, TopLevelSettings } from "./command";
|
|
2
2
|
import type { Guard } from "./guard";
|
|
3
|
+
import type { Localizable } from "./localization";
|
|
3
4
|
import type { Middleware } from "./middleware";
|
|
4
5
|
import type { OptionMap } from "./option";
|
|
5
6
|
/** A command module found on disk: `commands/config/set.ts` has path `["config", "set"]`. */
|
|
@@ -17,7 +18,7 @@ export interface CommandLeaf {
|
|
|
17
18
|
* Settings for a folder of commands (the loader will read them from a file in
|
|
18
19
|
* the folder). Guards and middleware apply to every command inside it.
|
|
19
20
|
*/
|
|
20
|
-
export interface CommandFolder {
|
|
21
|
+
export interface CommandFolder extends Localizable {
|
|
21
22
|
readonly path: readonly string[];
|
|
22
23
|
/** Shown in Discord's command picker. Defaults to the folder name. */
|
|
23
24
|
readonly description?: string;
|