@minesa-org/mini-interaction 0.4.12 → 0.4.16
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/core/http/DiscordRestClient.d.ts +11 -0
- package/dist/core/http/DiscordRestClient.js +72 -1
- package/dist/core/http/__tests__/discord-rest-client.messages.test.d.ts +1 -0
- package/dist/core/http/__tests__/discord-rest-client.messages.test.js +122 -0
- package/dist/core/interactions/InteractionContext.d.ts +5 -2
- package/dist/core/interactions/InteractionContext.js +5 -2
- package/dist/core/messages/DiscordSentMessage.d.ts +13 -0
- package/dist/core/messages/DiscordSentMessage.js +28 -0
- package/dist/core/messages/message-payloads.d.ts +40 -0
- package/dist/core/messages/message-payloads.js +60 -0
- package/dist/core/webhooks/DiscordWebhook.d.ts +10 -0
- package/dist/core/webhooks/DiscordWebhook.js +47 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { APIChannel } from 'discord-api-types/v10';
|
|
2
|
+
import { DiscordSentMessage } from '../messages/DiscordSentMessage.js';
|
|
3
|
+
import { type BaseDiscordMessageOptions, type DiscordReaction, type DiscordSendMessageOptions, type DiscordStartThreadOptions } from '../messages/message-payloads.js';
|
|
4
|
+
import { DiscordWebhook } from '../webhooks/DiscordWebhook.js';
|
|
1
5
|
type FetchLike = typeof fetch;
|
|
2
6
|
export type DiscordRestClientOptions = {
|
|
3
7
|
token: string;
|
|
@@ -18,5 +22,12 @@ export declare class DiscordRestClient {
|
|
|
18
22
|
private createRequestError;
|
|
19
23
|
createFollowup(interactionToken: string, body: unknown): Promise<unknown>;
|
|
20
24
|
editOriginal(interactionToken: string, body: unknown): Promise<unknown>;
|
|
25
|
+
createFollowupMessage(interactionToken: string, options: BaseDiscordMessageOptions): Promise<DiscordSentMessage>;
|
|
26
|
+
editOriginalMessage(interactionToken: string, options: BaseDiscordMessageOptions): Promise<DiscordSentMessage>;
|
|
27
|
+
sendMessage(options: DiscordSendMessageOptions): Promise<DiscordSentMessage>;
|
|
28
|
+
send(options: DiscordSendMessageOptions): Promise<DiscordSentMessage>;
|
|
29
|
+
startThread(options: DiscordStartThreadOptions): Promise<APIChannel>;
|
|
30
|
+
addReaction(channelId: string, messageId: string, reaction: DiscordReaction): Promise<void>;
|
|
31
|
+
webhook(id: string, token: string): DiscordWebhook;
|
|
21
32
|
}
|
|
22
33
|
export {};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { DiscordSentMessage } from '../messages/DiscordSentMessage.js';
|
|
3
|
+
import { createMessageRequestInit, } from '../messages/message-payloads.js';
|
|
4
|
+
import { DiscordWebhook } from '../webhooks/DiscordWebhook.js';
|
|
2
5
|
export class DiscordRestClient {
|
|
3
6
|
options;
|
|
4
7
|
fetchImpl;
|
|
@@ -20,7 +23,7 @@ export class DiscordRestClient {
|
|
|
20
23
|
...requestInit,
|
|
21
24
|
headers: {
|
|
22
25
|
...(authenticated ? { Authorization: `Bot ${this.options.token}` } : {}),
|
|
23
|
-
|
|
26
|
+
...getDefaultContentTypeHeader(requestInit.body),
|
|
24
27
|
...(requestInit.headers ?? {}),
|
|
25
28
|
},
|
|
26
29
|
});
|
|
@@ -74,4 +77,72 @@ export class DiscordRestClient {
|
|
|
74
77
|
authenticated: false,
|
|
75
78
|
});
|
|
76
79
|
}
|
|
80
|
+
async createFollowupMessage(interactionToken, options) {
|
|
81
|
+
const requestInit = createMessageRequestInit(options);
|
|
82
|
+
const message = await this.request(`/webhooks/${this.options.applicationId}/${interactionToken}`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
...requestInit,
|
|
85
|
+
authenticated: false,
|
|
86
|
+
});
|
|
87
|
+
return new DiscordSentMessage(this, message);
|
|
88
|
+
}
|
|
89
|
+
async editOriginalMessage(interactionToken, options) {
|
|
90
|
+
const requestInit = createMessageRequestInit(options);
|
|
91
|
+
const message = await this.request(`/webhooks/${this.options.applicationId}/${interactionToken}/messages/@original`, {
|
|
92
|
+
method: 'PATCH',
|
|
93
|
+
...requestInit,
|
|
94
|
+
authenticated: false,
|
|
95
|
+
});
|
|
96
|
+
return new DiscordSentMessage(this, message);
|
|
97
|
+
}
|
|
98
|
+
async sendMessage(options) {
|
|
99
|
+
const { channelId, ...messageOptions } = options;
|
|
100
|
+
const requestInit = createMessageRequestInit(messageOptions);
|
|
101
|
+
const message = await this.request(`/channels/${channelId}/messages`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
...requestInit,
|
|
104
|
+
});
|
|
105
|
+
return new DiscordSentMessage(this, message);
|
|
106
|
+
}
|
|
107
|
+
send(options) {
|
|
108
|
+
return this.sendMessage(options);
|
|
109
|
+
}
|
|
110
|
+
async startThread(options) {
|
|
111
|
+
const { channelId, messageId, reason, ...body } = options;
|
|
112
|
+
return this.request(`/channels/${channelId}/messages/${messageId}/threads`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
auto_archive_duration: body.autoArchiveDuration,
|
|
116
|
+
rate_limit_per_user: body.rateLimitPerUser,
|
|
117
|
+
name: body.name,
|
|
118
|
+
}),
|
|
119
|
+
headers: reason ? { 'X-Audit-Log-Reason': reason } : undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
addReaction(channelId, messageId, reaction) {
|
|
123
|
+
return this.request(`/channels/${channelId}/messages/${messageId}/reactions/${encodeDiscordReaction(reaction)}/@me`, {
|
|
124
|
+
method: 'PUT',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
webhook(id, token) {
|
|
128
|
+
return new DiscordWebhook(this, id, token);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function getDefaultContentTypeHeader(body) {
|
|
132
|
+
return body instanceof FormData ? {} : { 'Content-Type': 'application/json' };
|
|
133
|
+
}
|
|
134
|
+
function encodeDiscordReaction(reaction) {
|
|
135
|
+
if (typeof reaction !== 'string') {
|
|
136
|
+
return encodeURIComponent(reaction.id ? `${reaction.name}:${reaction.id}` : reaction.name);
|
|
137
|
+
}
|
|
138
|
+
const trimmed = reaction.trim();
|
|
139
|
+
const customEmojiMatch = trimmed.match(/^<a?:([^:>]+):(\d+)>$/);
|
|
140
|
+
if (customEmojiMatch) {
|
|
141
|
+
const [, name, id] = customEmojiMatch;
|
|
142
|
+
return encodeURIComponent(`${name}:${id}`);
|
|
143
|
+
}
|
|
144
|
+
if (/^[^:\s]+:\d+$/.test(trimmed)) {
|
|
145
|
+
return encodeURIComponent(trimmed);
|
|
146
|
+
}
|
|
147
|
+
return encodeURIComponent(trimmed);
|
|
77
148
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { MessageFlags } from "discord-api-types/v10";
|
|
4
|
+
import { ContainerBuilder, TextDisplayBuilder } from "../../../builders/index.js";
|
|
5
|
+
import { DiscordRestClient } from "../DiscordRestClient.js";
|
|
6
|
+
test("sendMessage sends channel messages and returns a thread-capable wrapper", async () => {
|
|
7
|
+
const calls = [];
|
|
8
|
+
const fetchImpl = (async (input, init) => {
|
|
9
|
+
calls.push({ input: String(input), init: init ?? {} });
|
|
10
|
+
if (calls.length === 1) {
|
|
11
|
+
return new Response(JSON.stringify({ id: "msg_1", channel_id: "chan_1" }), { status: 200 });
|
|
12
|
+
}
|
|
13
|
+
return new Response(JSON.stringify({ id: "thread_1", type: 11, parent_id: "chan_1" }), { status: 200 });
|
|
14
|
+
});
|
|
15
|
+
const rest = new DiscordRestClient({
|
|
16
|
+
token: "token",
|
|
17
|
+
applicationId: "app",
|
|
18
|
+
fetchImplementation: fetchImpl,
|
|
19
|
+
});
|
|
20
|
+
const container = new ContainerBuilder().addComponent(new TextDisplayBuilder().setContent("Hello"));
|
|
21
|
+
const sentMessage = await rest.send({
|
|
22
|
+
channelId: "chan_1",
|
|
23
|
+
components: [container],
|
|
24
|
+
flags: MessageFlags.IsComponentsV2,
|
|
25
|
+
});
|
|
26
|
+
assert.equal(sentMessage.id, "msg_1");
|
|
27
|
+
await sentMessage.startThread({
|
|
28
|
+
name: "Thread name",
|
|
29
|
+
autoArchiveDuration: 60,
|
|
30
|
+
reason: "Testing",
|
|
31
|
+
});
|
|
32
|
+
assert.equal(calls.length, 2);
|
|
33
|
+
assert.match(calls[0].input, /\/channels\/chan_1\/messages$/);
|
|
34
|
+
assert.match(calls[1].input, /\/channels\/chan_1\/messages\/msg_1\/threads$/);
|
|
35
|
+
assert.match(String(calls[0].init.body), /payload|components|flags/);
|
|
36
|
+
});
|
|
37
|
+
test("sentMessage.react supports custom emoji strings and emoji objects", async () => {
|
|
38
|
+
const calls = [];
|
|
39
|
+
const fetchImpl = (async (input, init) => {
|
|
40
|
+
calls.push({ input: String(input), init: init ?? {} });
|
|
41
|
+
if (calls.length === 1) {
|
|
42
|
+
return new Response(JSON.stringify({ id: "msg_react", channel_id: "chan_react" }), { status: 200 });
|
|
43
|
+
}
|
|
44
|
+
return new Response(null, { status: 204 });
|
|
45
|
+
});
|
|
46
|
+
const rest = new DiscordRestClient({
|
|
47
|
+
token: "token",
|
|
48
|
+
applicationId: "app",
|
|
49
|
+
fetchImplementation: fetchImpl,
|
|
50
|
+
});
|
|
51
|
+
const sentMessage = await rest.sendMessage({
|
|
52
|
+
channelId: "chan_react",
|
|
53
|
+
content: "React to me",
|
|
54
|
+
});
|
|
55
|
+
await sentMessage.react("<:wave:1234567890>");
|
|
56
|
+
await sentMessage.react({ name: "thumbsup", id: "999" });
|
|
57
|
+
assert.equal(calls.length, 3);
|
|
58
|
+
assert.match(calls[1].input, /\/channels\/chan_react\/messages\/msg_react\/reactions\/wave%3A1234567890\/@me$/);
|
|
59
|
+
assert.match(calls[2].input, /\/channels\/chan_react\/messages\/msg_react\/reactions\/thumbsup%3A999\/@me$/);
|
|
60
|
+
});
|
|
61
|
+
test("sendMessage uses multipart form-data when files are provided", async () => {
|
|
62
|
+
let capturedBody;
|
|
63
|
+
const fetchImpl = (async (_input, init) => {
|
|
64
|
+
capturedBody = init?.body;
|
|
65
|
+
return new Response(JSON.stringify({ id: "msg_file", channel_id: "chan_file" }), { status: 200 });
|
|
66
|
+
});
|
|
67
|
+
const rest = new DiscordRestClient({
|
|
68
|
+
token: "token",
|
|
69
|
+
applicationId: "app",
|
|
70
|
+
fetchImplementation: fetchImpl,
|
|
71
|
+
});
|
|
72
|
+
await rest.sendMessage({
|
|
73
|
+
channelId: "chan_file",
|
|
74
|
+
content: "with file",
|
|
75
|
+
files: [
|
|
76
|
+
{
|
|
77
|
+
name: "hello.txt",
|
|
78
|
+
data: new TextEncoder().encode("hello world"),
|
|
79
|
+
contentType: "text/plain",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
assert.ok(capturedBody instanceof FormData);
|
|
84
|
+
const payloadJson = capturedBody.get("payload_json");
|
|
85
|
+
assert.equal(typeof payloadJson, "string");
|
|
86
|
+
assert.match(String(payloadJson), /hello\.txt/);
|
|
87
|
+
});
|
|
88
|
+
test("webhook.send returns a sent message wrapper", async () => {
|
|
89
|
+
const calls = [];
|
|
90
|
+
const fetchImpl = (async (input, init) => {
|
|
91
|
+
calls.push({ input: String(input), init: init ?? {} });
|
|
92
|
+
return new Response(JSON.stringify({ id: "msg_hook", channel_id: "chan_hook" }), { status: 200 });
|
|
93
|
+
});
|
|
94
|
+
const rest = new DiscordRestClient({
|
|
95
|
+
token: "token",
|
|
96
|
+
applicationId: "app",
|
|
97
|
+
fetchImplementation: fetchImpl,
|
|
98
|
+
});
|
|
99
|
+
const webhook = rest.webhook("wh_1", "wh_token");
|
|
100
|
+
const sentMessage = await webhook.send({
|
|
101
|
+
content: "hello",
|
|
102
|
+
threadId: "thread_123",
|
|
103
|
+
username: "Mini",
|
|
104
|
+
});
|
|
105
|
+
assert.equal(sentMessage.id, "msg_hook");
|
|
106
|
+
assert.match(calls[0].input, /\/webhooks\/wh_1\/wh_token\?wait=true&thread_id=thread_123$/);
|
|
107
|
+
assert.match(String(calls[0].init.body), /"username":"Mini"/);
|
|
108
|
+
});
|
|
109
|
+
test("sendMessage rejects ephemeral flags for regular messages", async () => {
|
|
110
|
+
const rest = new DiscordRestClient({
|
|
111
|
+
token: "token",
|
|
112
|
+
applicationId: "app",
|
|
113
|
+
fetchImplementation: (async () => new Response(JSON.stringify({ id: "x", channel_id: "y" }), {
|
|
114
|
+
status: 200,
|
|
115
|
+
})),
|
|
116
|
+
});
|
|
117
|
+
await assert.rejects(() => rest.sendMessage({
|
|
118
|
+
channelId: "chan",
|
|
119
|
+
content: "nope",
|
|
120
|
+
flags: MessageFlags.Ephemeral,
|
|
121
|
+
}), /regular channel or webhook messages/);
|
|
122
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { APIInteractionResponse, APIInteractionResponseCallbackData, APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';
|
|
2
2
|
import type { ParsedInteraction } from '../../types/discord.js';
|
|
3
3
|
import { DiscordRestClient } from '../http/DiscordRestClient.js';
|
|
4
|
+
import type { DiscordSentMessage } from '../messages/DiscordSentMessage.js';
|
|
5
|
+
import type { BaseDiscordMessageOptions, DiscordSendMessageOptions } from '../messages/message-payloads.js';
|
|
4
6
|
export type InteractionContextOptions = {
|
|
5
7
|
interaction: ParsedInteraction;
|
|
6
8
|
rest: DiscordRestClient;
|
|
@@ -18,8 +20,9 @@ export declare class InteractionContext {
|
|
|
18
20
|
reply(data: APIInteractionResponseCallbackData): APIInteractionResponse;
|
|
19
21
|
deferReply(ephemeral?: boolean): APIInteractionResponse;
|
|
20
22
|
showModal(data: APIModalInteractionResponseCallbackData): APIInteractionResponse;
|
|
21
|
-
editReply(body:
|
|
22
|
-
followUp(body:
|
|
23
|
+
editReply(body: BaseDiscordMessageOptions): Promise<DiscordSentMessage>;
|
|
24
|
+
followUp(body: BaseDiscordMessageOptions): Promise<DiscordSentMessage>;
|
|
25
|
+
send(body: DiscordSendMessageOptions): Promise<DiscordSentMessage>;
|
|
23
26
|
get hasResponded(): boolean;
|
|
24
27
|
private clearAutoAck;
|
|
25
28
|
}
|
|
@@ -29,10 +29,13 @@ export class InteractionContext {
|
|
|
29
29
|
return { type: 9, data };
|
|
30
30
|
}
|
|
31
31
|
editReply(body) {
|
|
32
|
-
return this.options.rest.
|
|
32
|
+
return this.options.rest.editOriginalMessage(this.options.interaction.token, body);
|
|
33
33
|
}
|
|
34
34
|
followUp(body) {
|
|
35
|
-
return this.options.rest.
|
|
35
|
+
return this.options.rest.createFollowupMessage(this.options.interaction.token, body);
|
|
36
|
+
}
|
|
37
|
+
send(body) {
|
|
38
|
+
return this.options.rest.sendMessage(body);
|
|
36
39
|
}
|
|
37
40
|
get hasResponded() {
|
|
38
41
|
return this.responded;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { APIChannel, APIMessage } from "discord-api-types/v10";
|
|
2
|
+
import type { DiscordRestClient } from "../http/DiscordRestClient.js";
|
|
3
|
+
import type { DiscordReaction, DiscordStartThreadOptions } from "./message-payloads.js";
|
|
4
|
+
export declare class DiscordSentMessage {
|
|
5
|
+
private readonly rest;
|
|
6
|
+
readonly raw: APIMessage;
|
|
7
|
+
constructor(rest: DiscordRestClient, raw: APIMessage);
|
|
8
|
+
get id(): string;
|
|
9
|
+
get channelId(): string;
|
|
10
|
+
startThread(options: Omit<DiscordStartThreadOptions, "channelId" | "messageId">): Promise<APIChannel>;
|
|
11
|
+
react(reaction: DiscordReaction): Promise<this>;
|
|
12
|
+
toJSON(): APIMessage;
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class DiscordSentMessage {
|
|
2
|
+
rest;
|
|
3
|
+
raw;
|
|
4
|
+
constructor(rest, raw) {
|
|
5
|
+
this.rest = rest;
|
|
6
|
+
this.raw = raw;
|
|
7
|
+
}
|
|
8
|
+
get id() {
|
|
9
|
+
return this.raw.id;
|
|
10
|
+
}
|
|
11
|
+
get channelId() {
|
|
12
|
+
return this.raw.channel_id;
|
|
13
|
+
}
|
|
14
|
+
async startThread(options) {
|
|
15
|
+
return this.rest.startThread({
|
|
16
|
+
channelId: this.channelId,
|
|
17
|
+
messageId: this.id,
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async react(reaction) {
|
|
22
|
+
await this.rest.addReaction(this.channelId, this.id, reaction);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
toJSON() {
|
|
26
|
+
return this.raw;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { APIAllowedMentions } from "discord-api-types/v10";
|
|
2
|
+
import { type InteractionMessageData, type MessageFlagLike } from "../../utils/interactionMessageHelpers.js";
|
|
3
|
+
export type DiscordMessageFile = {
|
|
4
|
+
name: string;
|
|
5
|
+
data: ArrayBuffer | Blob | Buffer | Uint8Array;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
};
|
|
8
|
+
export type BaseDiscordMessageOptions = Omit<InteractionMessageData, "flags"> & {
|
|
9
|
+
flags?: MessageFlagLike | MessageFlagLike[];
|
|
10
|
+
allowedMentions?: APIAllowedMentions;
|
|
11
|
+
attachments?: Array<Record<string, unknown>>;
|
|
12
|
+
stickerIds?: string[];
|
|
13
|
+
files?: DiscordMessageFile[];
|
|
14
|
+
};
|
|
15
|
+
export type DiscordSendMessageOptions = BaseDiscordMessageOptions & {
|
|
16
|
+
channelId: string;
|
|
17
|
+
};
|
|
18
|
+
export type DiscordStartThreadOptions = {
|
|
19
|
+
channelId: string;
|
|
20
|
+
messageId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
autoArchiveDuration?: number;
|
|
23
|
+
rateLimitPerUser?: number;
|
|
24
|
+
reason?: string;
|
|
25
|
+
};
|
|
26
|
+
export type DiscordWebhookSendOptions = BaseDiscordMessageOptions & {
|
|
27
|
+
threadId?: string;
|
|
28
|
+
username?: string;
|
|
29
|
+
avatarUrl?: string;
|
|
30
|
+
};
|
|
31
|
+
export type DiscordReaction = string | {
|
|
32
|
+
name: string;
|
|
33
|
+
id?: string;
|
|
34
|
+
animated?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare function normaliseDiscordMessagePayload(options: BaseDiscordMessageOptions): Record<string, unknown>;
|
|
37
|
+
export declare function createMessageRequestInit(options: BaseDiscordMessageOptions): {
|
|
38
|
+
body: BodyInit;
|
|
39
|
+
headers?: HeadersInit;
|
|
40
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MessageFlags } from "discord-api-types/v10";
|
|
2
|
+
import { normaliseInteractionMessageData, } from "../../utils/interactionMessageHelpers.js";
|
|
3
|
+
export function normaliseDiscordMessagePayload(options) {
|
|
4
|
+
const payload = normaliseInteractionMessageData({
|
|
5
|
+
content: options.content,
|
|
6
|
+
components: options.components,
|
|
7
|
+
embeds: options.embeds,
|
|
8
|
+
flags: options.flags,
|
|
9
|
+
});
|
|
10
|
+
const resolvedPayload = payload ? { ...payload } : {};
|
|
11
|
+
const flags = resolvedPayload.flags;
|
|
12
|
+
if (typeof flags === "number" && (flags & MessageFlags.Ephemeral) === MessageFlags.Ephemeral) {
|
|
13
|
+
throw new Error("[MiniInteraction] Ephemeral flags are not supported for regular channel or webhook messages.");
|
|
14
|
+
}
|
|
15
|
+
if (options.allowedMentions) {
|
|
16
|
+
resolvedPayload.allowed_mentions = options.allowedMentions;
|
|
17
|
+
}
|
|
18
|
+
if (options.stickerIds && options.stickerIds.length > 0) {
|
|
19
|
+
resolvedPayload.sticker_ids = options.stickerIds;
|
|
20
|
+
}
|
|
21
|
+
const files = options.files ?? [];
|
|
22
|
+
if (options.attachments && options.attachments.length > 0) {
|
|
23
|
+
resolvedPayload.attachments = options.attachments;
|
|
24
|
+
}
|
|
25
|
+
else if (files.length > 0) {
|
|
26
|
+
resolvedPayload.attachments = files.map((file, index) => ({
|
|
27
|
+
id: String(index),
|
|
28
|
+
filename: file.name,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
return resolvedPayload;
|
|
32
|
+
}
|
|
33
|
+
export function createMessageRequestInit(options) {
|
|
34
|
+
const payload = normaliseDiscordMessagePayload(options);
|
|
35
|
+
const files = options.files ?? [];
|
|
36
|
+
if (files.length === 0) {
|
|
37
|
+
return {
|
|
38
|
+
body: JSON.stringify(payload),
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const formData = new FormData();
|
|
45
|
+
formData.set("payload_json", JSON.stringify(payload));
|
|
46
|
+
files.forEach((file, index) => {
|
|
47
|
+
formData.append(`files[${index}]`, toBlob(file), file.name);
|
|
48
|
+
});
|
|
49
|
+
return { body: formData };
|
|
50
|
+
}
|
|
51
|
+
function toBlob(file) {
|
|
52
|
+
if (file.data instanceof Blob) {
|
|
53
|
+
return file.data;
|
|
54
|
+
}
|
|
55
|
+
if (file.data instanceof ArrayBuffer) {
|
|
56
|
+
return new Blob([file.data], file.contentType ? { type: file.contentType } : undefined);
|
|
57
|
+
}
|
|
58
|
+
const bytes = Uint8Array.from(file.data);
|
|
59
|
+
return new Blob([bytes.buffer], file.contentType ? { type: file.contentType } : undefined);
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DiscordRestClient } from "../http/DiscordRestClient.js";
|
|
2
|
+
import { DiscordSentMessage } from "../messages/DiscordSentMessage.js";
|
|
3
|
+
import { type DiscordWebhookSendOptions } from "../messages/message-payloads.js";
|
|
4
|
+
export declare class DiscordWebhook {
|
|
5
|
+
private readonly rest;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly token: string;
|
|
8
|
+
constructor(rest: DiscordRestClient, id: string, token: string);
|
|
9
|
+
send(options: DiscordWebhookSendOptions): Promise<DiscordSentMessage>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DiscordSentMessage } from "../messages/DiscordSentMessage.js";
|
|
2
|
+
import { createMessageRequestInit, } from "../messages/message-payloads.js";
|
|
3
|
+
export class DiscordWebhook {
|
|
4
|
+
rest;
|
|
5
|
+
id;
|
|
6
|
+
token;
|
|
7
|
+
constructor(rest, id, token) {
|
|
8
|
+
this.rest = rest;
|
|
9
|
+
this.id = id;
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
async send(options) {
|
|
13
|
+
const { threadId, username, avatarUrl, ...messageOptions } = options;
|
|
14
|
+
const requestInit = createMessageRequestInit(messageOptions);
|
|
15
|
+
const searchParams = new URLSearchParams({ wait: "true" });
|
|
16
|
+
if (threadId) {
|
|
17
|
+
searchParams.set("thread_id", threadId);
|
|
18
|
+
}
|
|
19
|
+
const message = await this.rest.request(`/webhooks/${this.id}/${this.token}?${searchParams.toString()}`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
body: appendWebhookPayload(requestInit.body, {
|
|
22
|
+
username,
|
|
23
|
+
avatar_url: avatarUrl,
|
|
24
|
+
}),
|
|
25
|
+
headers: requestInit.headers,
|
|
26
|
+
authenticated: false,
|
|
27
|
+
});
|
|
28
|
+
return new DiscordSentMessage(this.rest, message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function appendWebhookPayload(body, webhookOverrides) {
|
|
32
|
+
if (body instanceof FormData) {
|
|
33
|
+
const payload = JSON.parse(String(body.get("payload_json") ?? "{}"));
|
|
34
|
+
if (webhookOverrides.username)
|
|
35
|
+
payload.username = webhookOverrides.username;
|
|
36
|
+
if (webhookOverrides.avatar_url)
|
|
37
|
+
payload.avatar_url = webhookOverrides.avatar_url;
|
|
38
|
+
body.set("payload_json", JSON.stringify(payload));
|
|
39
|
+
return body;
|
|
40
|
+
}
|
|
41
|
+
const payload = JSON.parse(String(body));
|
|
42
|
+
if (webhookOverrides.username)
|
|
43
|
+
payload.username = webhookOverrides.username;
|
|
44
|
+
if (webhookOverrides.avatar_url)
|
|
45
|
+
payload.avatar_url = webhookOverrides.avatar_url;
|
|
46
|
+
return JSON.stringify(payload);
|
|
47
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { ChannelType } from "./types/ChannelType.js";
|
|
|
12
12
|
export { InteractionFlags, } from "./types/InteractionFlags.js";
|
|
13
13
|
export { ButtonStyle } from "./types/ButtonStyle.js";
|
|
14
14
|
export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
|
|
15
|
+
export { MessageFlags, type APIAllowedMentions } from "discord-api-types/v10";
|
|
15
16
|
export { TextInputStyle } from "discord-api-types/v10";
|
|
16
17
|
export { MiniPermFlags } from "./types/PermissionFlags.js";
|
|
17
18
|
export type { ActionRowComponent, MessageActionRowComponent, InteractionComponentData, } from "./types/ComponentTypes.js";
|
|
@@ -26,6 +27,9 @@ export type { OAuthConfig, OAuthTokens, DiscordUser, } from "./oauth/DiscordOAut
|
|
|
26
27
|
export { OAuthTokenStorage } from "./oauth/OAuthTokenStorage.js";
|
|
27
28
|
export { DiscordRestClient } from "./core/http/DiscordRestClient.js";
|
|
28
29
|
export type { DiscordRestClientOptions } from "./core/http/DiscordRestClient.js";
|
|
30
|
+
export { DiscordSentMessage } from "./core/messages/DiscordSentMessage.js";
|
|
31
|
+
export type { DiscordMessageFile, DiscordReaction, DiscordSendMessageOptions, DiscordStartThreadOptions, DiscordWebhookSendOptions, } from "./core/messages/message-payloads.js";
|
|
32
|
+
export { DiscordWebhook } from "./core/webhooks/DiscordWebhook.js";
|
|
29
33
|
export { InteractionContext } from "./core/interactions/InteractionContext.js";
|
|
30
34
|
export type { InteractionContextOptions } from "./core/interactions/InteractionContext.js";
|
|
31
35
|
export { verifyAndParseInteraction } from "./core/interactions/InteractionVerifier.js";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { ChannelType } from "./types/ChannelType.js";
|
|
|
10
10
|
export { InteractionFlags, } from "./types/InteractionFlags.js";
|
|
11
11
|
export { ButtonStyle } from "./types/ButtonStyle.js";
|
|
12
12
|
export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
|
|
13
|
+
export { MessageFlags } from "discord-api-types/v10";
|
|
13
14
|
export { TextInputStyle } from "discord-api-types/v10";
|
|
14
15
|
export { MiniPermFlags } from "./types/PermissionFlags.js";
|
|
15
16
|
export * from "./builders/index.js";
|
|
@@ -20,6 +21,8 @@ export { generateOAuthUrl, getOAuthTokens, refreshAccessToken, getDiscordUser, e
|
|
|
20
21
|
export { OAuthTokenStorage } from "./oauth/OAuthTokenStorage.js";
|
|
21
22
|
// New v10 core modules
|
|
22
23
|
export { DiscordRestClient } from "./core/http/DiscordRestClient.js";
|
|
24
|
+
export { DiscordSentMessage } from "./core/messages/DiscordSentMessage.js";
|
|
25
|
+
export { DiscordWebhook } from "./core/webhooks/DiscordWebhook.js";
|
|
23
26
|
export { InteractionContext } from "./core/interactions/InteractionContext.js";
|
|
24
27
|
export { verifyAndParseInteraction } from "./core/interactions/InteractionVerifier.js";
|
|
25
28
|
export { InteractionRouter } from "./router/InteractionRouter.js";
|
package/package.json
CHANGED