@gakr-gakr/discord 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/account-inspect-api.ts +6 -0
- package/action-runtime-api.ts +1 -0
- package/api.ts +130 -0
- package/autobot.plugin.json +15 -0
- package/channel-config-api.ts +1 -0
- package/channel-plugin-api.ts +3 -0
- package/config-api.ts +4 -0
- package/configured-state.ts +6 -0
- package/contract-api.ts +21 -0
- package/directory-contract-api.ts +4 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +24 -0
- package/package.json +79 -0
- package/runtime-api.actions.ts +15 -0
- package/runtime-api.lookup.ts +22 -0
- package/runtime-api.monitor.ts +50 -0
- package/runtime-api.send.ts +79 -0
- package/runtime-api.threads.ts +31 -0
- package/runtime-api.ts +181 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +4 -0
- package/security-audit-contract-api.ts +1 -0
- package/security-contract-api.ts +4 -0
- package/session-key-api.ts +1 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-inspect.ts +131 -0
- package/src/accounts.ts +205 -0
- package/src/actions/handle-action.guild-admin.ts +421 -0
- package/src/actions/handle-action.ts +402 -0
- package/src/actions/runtime.guild.ts +446 -0
- package/src/actions/runtime.messaging.messages.ts +226 -0
- package/src/actions/runtime.messaging.reactions.ts +67 -0
- package/src/actions/runtime.messaging.runtime.ts +73 -0
- package/src/actions/runtime.messaging.send.ts +336 -0
- package/src/actions/runtime.messaging.shared.ts +97 -0
- package/src/actions/runtime.messaging.ts +37 -0
- package/src/actions/runtime.moderation-shared.ts +48 -0
- package/src/actions/runtime.moderation.ts +116 -0
- package/src/actions/runtime.presence.ts +117 -0
- package/src/actions/runtime.shared.ts +86 -0
- package/src/actions/runtime.ts +87 -0
- package/src/api.ts +219 -0
- package/src/approval-handler.runtime.ts +636 -0
- package/src/approval-native.ts +219 -0
- package/src/approval-runtime.ts +14 -0
- package/src/approval-shared.ts +56 -0
- package/src/audit-core.ts +178 -0
- package/src/audit.ts +32 -0
- package/src/channel-actions.runtime.ts +1 -0
- package/src/channel-actions.ts +254 -0
- package/src/channel-api.ts +29 -0
- package/src/channel.conversation.ts +159 -0
- package/src/channel.loaders.ts +50 -0
- package/src/channel.runtime.ts +1 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.ts +728 -0
- package/src/chunk.ts +321 -0
- package/src/client.ts +143 -0
- package/src/component-custom-id.ts +72 -0
- package/src/components-registry.ts +356 -0
- package/src/components.builders.ts +410 -0
- package/src/components.modal.ts +124 -0
- package/src/components.parse.ts +407 -0
- package/src/components.ts +54 -0
- package/src/components.types.ts +187 -0
- package/src/config-schema.ts +6 -0
- package/src/config-ui-hints.ts +354 -0
- package/src/conversation-identity.ts +58 -0
- package/src/delivery-retry.ts +56 -0
- package/src/directory-cache.ts +116 -0
- package/src/directory-config.ts +58 -0
- package/src/directory-live.ts +135 -0
- package/src/doctor-contract.ts +477 -0
- package/src/doctor-shared.ts +5 -0
- package/src/doctor.ts +340 -0
- package/src/draft-chunking.ts +43 -0
- package/src/draft-stream.ts +162 -0
- package/src/error-body.ts +38 -0
- package/src/exec-approvals.ts +110 -0
- package/src/gateway-logging.ts +67 -0
- package/src/group-policy.ts +113 -0
- package/src/guilds.ts +29 -0
- package/src/inbound-event-delivery.ts +135 -0
- package/src/interactive-dispatch.ts +104 -0
- package/src/internal/api.commands.ts +51 -0
- package/src/internal/api.guild.ts +164 -0
- package/src/internal/api.interactions.ts +53 -0
- package/src/internal/api.messages.ts +113 -0
- package/src/internal/api.reactions.ts +38 -0
- package/src/internal/api.ts +61 -0
- package/src/internal/api.users.ts +19 -0
- package/src/internal/api.webhooks.ts +13 -0
- package/src/internal/client.ts +310 -0
- package/src/internal/command-deploy.ts +352 -0
- package/src/internal/commands.ts +188 -0
- package/src/internal/components.base.ts +65 -0
- package/src/internal/components.message.ts +279 -0
- package/src/internal/components.modal.ts +95 -0
- package/src/internal/components.ts +31 -0
- package/src/internal/discord.ts +11 -0
- package/src/internal/embeds.ts +35 -0
- package/src/internal/entity-cache.ts +98 -0
- package/src/internal/event-queue.ts +185 -0
- package/src/internal/gateway-close-codes.ts +25 -0
- package/src/internal/gateway-dispatch.ts +96 -0
- package/src/internal/gateway-identify-limiter.ts +26 -0
- package/src/internal/gateway-lifecycle.ts +75 -0
- package/src/internal/gateway-rate-limit.ts +104 -0
- package/src/internal/gateway.ts +479 -0
- package/src/internal/interaction-dispatch.ts +162 -0
- package/src/internal/interaction-options.ts +98 -0
- package/src/internal/interaction-response.ts +53 -0
- package/src/internal/interactions.ts +378 -0
- package/src/internal/listeners.ts +91 -0
- package/src/internal/modal-fields.ts +95 -0
- package/src/internal/payload.ts +69 -0
- package/src/internal/rest-body.ts +115 -0
- package/src/internal/rest-errors.ts +88 -0
- package/src/internal/rest-routes.ts +50 -0
- package/src/internal/rest-scheduler.ts +557 -0
- package/src/internal/rest.ts +322 -0
- package/src/internal/schemas.ts +36 -0
- package/src/internal/structures.ts +280 -0
- package/src/internal/test-builders.test-support.ts +167 -0
- package/src/internal/voice.ts +49 -0
- package/src/media-detection.ts +28 -0
- package/src/mentions.ts +147 -0
- package/src/monitor/ack-reactions.ts +70 -0
- package/src/monitor/agent-components-auth.ts +7 -0
- package/src/monitor/agent-components-context.ts +154 -0
- package/src/monitor/agent-components-data.ts +224 -0
- package/src/monitor/agent-components-dm-auth.ts +177 -0
- package/src/monitor/agent-components-guild-auth.ts +322 -0
- package/src/monitor/agent-components-helpers.runtime.ts +3 -0
- package/src/monitor/agent-components-helpers.ts +34 -0
- package/src/monitor/agent-components-reply.ts +10 -0
- package/src/monitor/agent-components.deps.runtime.ts +2 -0
- package/src/monitor/agent-components.dispatch.ts +359 -0
- package/src/monitor/agent-components.handlers.ts +303 -0
- package/src/monitor/agent-components.modal.ts +160 -0
- package/src/monitor/agent-components.plugin-interactive.ts +187 -0
- package/src/monitor/agent-components.runtime.ts +14 -0
- package/src/monitor/agent-components.system-controls.ts +215 -0
- package/src/monitor/agent-components.ts +70 -0
- package/src/monitor/agent-components.types.ts +58 -0
- package/src/monitor/agent-components.wildcard-controls.ts +171 -0
- package/src/monitor/allow-list.ts +631 -0
- package/src/monitor/auto-presence.ts +356 -0
- package/src/monitor/channel-access.ts +102 -0
- package/src/monitor/commands.ts +9 -0
- package/src/monitor/dm-command-auth.ts +259 -0
- package/src/monitor/dm-command-decision.ts +49 -0
- package/src/monitor/exec-approvals.ts +161 -0
- package/src/monitor/format.ts +45 -0
- package/src/monitor/gateway-handle.ts +34 -0
- package/src/monitor/gateway-metadata.ts +298 -0
- package/src/monitor/gateway-plugin.ts +302 -0
- package/src/monitor/gateway-registry.ts +37 -0
- package/src/monitor/gateway-supervisor.ts +206 -0
- package/src/monitor/inbound-context.ts +95 -0
- package/src/monitor/inbound-dedupe.ts +79 -0
- package/src/monitor/inbound-job.ts +118 -0
- package/src/monitor/listeners.queue.ts +91 -0
- package/src/monitor/listeners.reactions.ts +594 -0
- package/src/monitor/listeners.ts +150 -0
- package/src/monitor/message-channel-info.ts +96 -0
- package/src/monitor/message-forwarded.ts +114 -0
- package/src/monitor/message-handler.batch-gate.ts +19 -0
- package/src/monitor/message-handler.context.ts +492 -0
- package/src/monitor/message-handler.dm-preflight.ts +119 -0
- package/src/monitor/message-handler.draft-preview.ts +436 -0
- package/src/monitor/message-handler.hydration.ts +198 -0
- package/src/monitor/message-handler.module-test-helpers.ts +31 -0
- package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
- package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
- package/src/monitor/message-handler.preflight-context.ts +54 -0
- package/src/monitor/message-handler.preflight-helpers.ts +164 -0
- package/src/monitor/message-handler.preflight-history.ts +23 -0
- package/src/monitor/message-handler.preflight-logging.ts +36 -0
- package/src/monitor/message-handler.preflight-pluralkit.ts +28 -0
- package/src/monitor/message-handler.preflight-runtime.ts +28 -0
- package/src/monitor/message-handler.preflight-thread.ts +49 -0
- package/src/monitor/message-handler.preflight.ts +822 -0
- package/src/monitor/message-handler.preflight.types.ts +115 -0
- package/src/monitor/message-handler.process.ts +1033 -0
- package/src/monitor/message-handler.routing-preflight.ts +112 -0
- package/src/monitor/message-handler.ts +309 -0
- package/src/monitor/message-media.ts +536 -0
- package/src/monitor/message-run-queue.ts +101 -0
- package/src/monitor/message-text.ts +171 -0
- package/src/monitor/message-utils.ts +34 -0
- package/src/monitor/model-picker-preferences.ts +184 -0
- package/src/monitor/model-picker.state.ts +364 -0
- package/src/monitor/model-picker.test-utils.ts +26 -0
- package/src/monitor/model-picker.ts +38 -0
- package/src/monitor/model-picker.view.ts +722 -0
- package/src/monitor/native-command-agent-reply.ts +125 -0
- package/src/monitor/native-command-arg-ui.ts +233 -0
- package/src/monitor/native-command-auth.ts +309 -0
- package/src/monitor/native-command-bypass.ts +13 -0
- package/src/monitor/native-command-context.ts +109 -0
- package/src/monitor/native-command-dispatch.ts +35 -0
- package/src/monitor/native-command-model-picker-apply.ts +209 -0
- package/src/monitor/native-command-model-picker-interaction.ts +516 -0
- package/src/monitor/native-command-model-picker-ui.ts +357 -0
- package/src/monitor/native-command-reply.ts +185 -0
- package/src/monitor/native-command-route.ts +91 -0
- package/src/monitor/native-command-status.ts +76 -0
- package/src/monitor/native-command-ui.ts +26 -0
- package/src/monitor/native-command-ui.types.ts +20 -0
- package/src/monitor/native-command.args.ts +45 -0
- package/src/monitor/native-command.options.ts +153 -0
- package/src/monitor/native-command.runtime.ts +51 -0
- package/src/monitor/native-command.ts +747 -0
- package/src/monitor/native-command.types.ts +9 -0
- package/src/monitor/native-interaction-channel-context.ts +50 -0
- package/src/monitor/preflight-audio.runtime.ts +9 -0
- package/src/monitor/preflight-audio.ts +130 -0
- package/src/monitor/presence-cache.ts +61 -0
- package/src/monitor/presence.ts +50 -0
- package/src/monitor/provider-session.runtime.ts +12 -0
- package/src/monitor/provider.acp.ts +89 -0
- package/src/monitor/provider.allowlist.ts +398 -0
- package/src/monitor/provider.cleanup.ts +41 -0
- package/src/monitor/provider.commands.ts +129 -0
- package/src/monitor/provider.config-log.ts +45 -0
- package/src/monitor/provider.deploy-errors.ts +362 -0
- package/src/monitor/provider.deploy.ts +221 -0
- package/src/monitor/provider.interactions.ts +160 -0
- package/src/monitor/provider.lifecycle.ts +562 -0
- package/src/monitor/provider.runtime.ts +1 -0
- package/src/monitor/provider.startup-log.ts +32 -0
- package/src/monitor/provider.startup.ts +323 -0
- package/src/monitor/provider.ts +688 -0
- package/src/monitor/reply-context.ts +64 -0
- package/src/monitor/reply-delivery.ts +216 -0
- package/src/monitor/reply-safety.ts +96 -0
- package/src/monitor/rest-fetch.ts +97 -0
- package/src/monitor/route-resolution.ts +140 -0
- package/src/monitor/sender-identity.ts +81 -0
- package/src/monitor/startup-status.ts +10 -0
- package/src/monitor/status.ts +22 -0
- package/src/monitor/system-events.ts +55 -0
- package/src/monitor/thread-bindings.config.ts +35 -0
- package/src/monitor/thread-bindings.discord-api.ts +310 -0
- package/src/monitor/thread-bindings.lifecycle.ts +354 -0
- package/src/monitor/thread-bindings.manager.ts +554 -0
- package/src/monitor/thread-bindings.messages.ts +6 -0
- package/src/monitor/thread-bindings.persona.ts +25 -0
- package/src/monitor/thread-bindings.session-adapter.ts +229 -0
- package/src/monitor/thread-bindings.session-shared.ts +59 -0
- package/src/monitor/thread-bindings.session-updates.ts +35 -0
- package/src/monitor/thread-bindings.state.ts +540 -0
- package/src/monitor/thread-bindings.ts +48 -0
- package/src/monitor/thread-bindings.types.ts +83 -0
- package/src/monitor/thread-channel-context.ts +112 -0
- package/src/monitor/thread-session-close.ts +63 -0
- package/src/monitor/thread-title.ts +181 -0
- package/src/monitor/threading.auto-thread.ts +287 -0
- package/src/monitor/threading.cache.ts +45 -0
- package/src/monitor/threading.starter.ts +288 -0
- package/src/monitor/threading.ts +20 -0
- package/src/monitor/threading.types.ts +102 -0
- package/src/monitor/timeouts.ts +84 -0
- package/src/monitor/typing.ts +17 -0
- package/src/monitor.gateway.ts +75 -0
- package/src/monitor.ts +28 -0
- package/src/network-config.ts +79 -0
- package/src/normalize.ts +86 -0
- package/src/outbound-adapter.ts +327 -0
- package/src/outbound-approval.ts +29 -0
- package/src/outbound-components.ts +86 -0
- package/src/outbound-payload.ts +208 -0
- package/src/outbound-send-context.ts +92 -0
- package/src/outbound-session-route.ts +72 -0
- package/src/pluralkit.ts +58 -0
- package/src/preview-streaming.ts +18 -0
- package/src/probe.runtime.ts +1 -0
- package/src/probe.ts +237 -0
- package/src/proxy-fetch.ts +92 -0
- package/src/proxy-request-client.ts +21 -0
- package/src/recipient-resolution.ts +39 -0
- package/src/resolve-allowlist-common.ts +39 -0
- package/src/resolve-channels.ts +369 -0
- package/src/resolve-users.ts +184 -0
- package/src/retry.ts +98 -0
- package/src/runtime-api.ts +64 -0
- package/src/runtime-config.ts +16 -0
- package/src/runtime.ts +23 -0
- package/src/secret-config-contract.ts +140 -0
- package/src/security-audit.runtime.ts +1 -0
- package/src/security-audit.ts +208 -0
- package/src/security-contract.ts +47 -0
- package/src/security-doctor.ts +20 -0
- package/src/security.ts +60 -0
- package/src/send-target-parsing.ts +14 -0
- package/src/send.channels.ts +139 -0
- package/src/send.components.ts +391 -0
- package/src/send.emojis-stickers.ts +57 -0
- package/src/send.guild.ts +170 -0
- package/src/send.message-request.ts +112 -0
- package/src/send.messages.ts +229 -0
- package/src/send.outbound.ts +459 -0
- package/src/send.permissions.ts +283 -0
- package/src/send.reactions.ts +155 -0
- package/src/send.receipt.ts +69 -0
- package/src/send.shared.ts +469 -0
- package/src/send.ts +82 -0
- package/src/send.types.ts +191 -0
- package/src/send.typing.ts +9 -0
- package/src/send.voice.ts +140 -0
- package/src/send.webhook.ts +137 -0
- package/src/session-contract.ts +3 -0
- package/src/session-key-normalization.ts +47 -0
- package/src/setup-account-state.ts +144 -0
- package/src/setup-adapter.ts +14 -0
- package/src/setup-core.ts +215 -0
- package/src/setup-runtime-helpers.ts +10 -0
- package/src/setup-surface.ts +132 -0
- package/src/shared-interactive.ts +167 -0
- package/src/shared.ts +197 -0
- package/src/status-issues.ts +201 -0
- package/src/subagent-hooks.ts +232 -0
- package/src/target-parsing.ts +70 -0
- package/src/target-resolver.ts +129 -0
- package/src/targets.ts +12 -0
- package/src/token.ts +107 -0
- package/src/ui-colors.ts +27 -0
- package/src/ui.ts +20 -0
- package/src/voice/access.ts +126 -0
- package/src/voice/audio.ts +249 -0
- package/src/voice/capture-state.ts +120 -0
- package/src/voice/command.ts +284 -0
- package/src/voice/config.ts +8 -0
- package/src/voice/ingress.ts +164 -0
- package/src/voice/manager.runtime.ts +14 -0
- package/src/voice/manager.ts +1155 -0
- package/src/voice/prompt.ts +22 -0
- package/src/voice/realtime.ts +1370 -0
- package/src/voice/receive-recovery.ts +159 -0
- package/src/voice/sanitize.ts +29 -0
- package/src/voice/sdk-runtime.ts +14 -0
- package/src/voice/segment.ts +160 -0
- package/src/voice/session.ts +81 -0
- package/src/voice/speaker-context.ts +127 -0
- package/src/voice/tts.ts +151 -0
- package/src/voice-message.ts +474 -0
- package/subagent-hooks-api.ts +27 -0
- package/test-api.ts +4 -0
- package/thread-binding-api.ts +1 -0
- package/timeouts.ts +6 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachChannelToResult,
|
|
3
|
+
type ChannelOutboundAdapter,
|
|
4
|
+
} from "autobot/plugin-sdk/channel-send-result";
|
|
5
|
+
import {
|
|
6
|
+
resolvePayloadMediaUrls,
|
|
7
|
+
sendPayloadMediaSequenceOrFallback,
|
|
8
|
+
sendTextMediaPayload,
|
|
9
|
+
} from "autobot/plugin-sdk/reply-payload";
|
|
10
|
+
import { normalizeOptionalString } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
11
|
+
import { normalizeDiscordApprovalPayload } from "./outbound-approval.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveDiscordComponentSpec,
|
|
14
|
+
sendDiscordComponentMessageLazy,
|
|
15
|
+
} from "./outbound-components.js";
|
|
16
|
+
import { createDiscordPayloadSendContext } from "./outbound-send-context.js";
|
|
17
|
+
import { createDiscordSendReceipt } from "./send.receipt.js";
|
|
18
|
+
import type { DiscordSendComponents, DiscordSendEmbeds } from "./send.shared.js";
|
|
19
|
+
|
|
20
|
+
export async function sendDiscordOutboundPayload(params: {
|
|
21
|
+
ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendPayload"]>>[0];
|
|
22
|
+
fallbackAdapter: ChannelOutboundAdapter;
|
|
23
|
+
}): Promise<Awaited<ReturnType<NonNullable<ChannelOutboundAdapter["sendPayload"]>>>> {
|
|
24
|
+
const ctx = params.ctx;
|
|
25
|
+
const payload = normalizeDiscordApprovalPayload({
|
|
26
|
+
...ctx.payload,
|
|
27
|
+
text: ctx.payload.text ?? "",
|
|
28
|
+
});
|
|
29
|
+
const mediaUrls = resolvePayloadMediaUrls(payload);
|
|
30
|
+
const sendContext = await createDiscordPayloadSendContext(ctx);
|
|
31
|
+
|
|
32
|
+
if (payload.audioAsVoice && mediaUrls.length > 0) {
|
|
33
|
+
let lastResult = await sendContext.withRetry(
|
|
34
|
+
async () =>
|
|
35
|
+
await sendContext.sendVoice(sendContext.target, mediaUrls[0], {
|
|
36
|
+
cfg: ctx.cfg,
|
|
37
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
38
|
+
accountId: ctx.accountId ?? undefined,
|
|
39
|
+
silent: ctx.silent ?? undefined,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
if (payload.text?.trim()) {
|
|
43
|
+
lastResult = await sendContext.withRetry(
|
|
44
|
+
async () =>
|
|
45
|
+
await sendContext.send(sendContext.target, payload.text, {
|
|
46
|
+
verbose: false,
|
|
47
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
48
|
+
accountId: ctx.accountId ?? undefined,
|
|
49
|
+
silent: ctx.silent ?? undefined,
|
|
50
|
+
cfg: ctx.cfg,
|
|
51
|
+
...sendContext.formatting,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
for (const mediaUrl of mediaUrls.slice(1)) {
|
|
56
|
+
lastResult = await sendContext.withRetry(
|
|
57
|
+
async () =>
|
|
58
|
+
await sendContext.send(sendContext.target, "", {
|
|
59
|
+
verbose: false,
|
|
60
|
+
mediaUrl,
|
|
61
|
+
mediaAccess: ctx.mediaAccess,
|
|
62
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
63
|
+
mediaReadFile: ctx.mediaReadFile,
|
|
64
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
65
|
+
accountId: ctx.accountId ?? undefined,
|
|
66
|
+
silent: ctx.silent ?? undefined,
|
|
67
|
+
cfg: ctx.cfg,
|
|
68
|
+
...sendContext.formatting,
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return attachChannelToResult("discord", lastResult);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const componentSpec = await resolveDiscordComponentSpec(payload);
|
|
76
|
+
if (!componentSpec) {
|
|
77
|
+
const discordData =
|
|
78
|
+
payload.channelData?.discord &&
|
|
79
|
+
typeof payload.channelData.discord === "object" &&
|
|
80
|
+
!Array.isArray(payload.channelData.discord)
|
|
81
|
+
? (payload.channelData.discord as Record<string, unknown>)
|
|
82
|
+
: {};
|
|
83
|
+
const nativeComponents = Array.isArray(discordData.components)
|
|
84
|
+
? (discordData.components as DiscordSendComponents)
|
|
85
|
+
: undefined;
|
|
86
|
+
const embeds = Array.isArray(discordData.embeds)
|
|
87
|
+
? (discordData.embeds as DiscordSendEmbeds)
|
|
88
|
+
: undefined;
|
|
89
|
+
const filename = normalizeOptionalString(discordData.filename);
|
|
90
|
+
if (nativeComponents || embeds?.length || filename) {
|
|
91
|
+
const result = await sendPayloadMediaSequenceOrFallback({
|
|
92
|
+
text: payload.text ?? "",
|
|
93
|
+
mediaUrls,
|
|
94
|
+
fallbackResult: {
|
|
95
|
+
messageId: "",
|
|
96
|
+
channelId: sendContext.target,
|
|
97
|
+
receipt: createDiscordSendReceipt({
|
|
98
|
+
platformMessageIds: [],
|
|
99
|
+
channelId: sendContext.target,
|
|
100
|
+
kind: "unknown",
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
sendNoMedia: async () =>
|
|
104
|
+
await sendContext.withRetry(
|
|
105
|
+
async () =>
|
|
106
|
+
await sendContext.send(sendContext.target, payload.text ?? "", {
|
|
107
|
+
verbose: false,
|
|
108
|
+
components: nativeComponents,
|
|
109
|
+
embeds,
|
|
110
|
+
filename,
|
|
111
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
112
|
+
accountId: ctx.accountId ?? undefined,
|
|
113
|
+
silent: ctx.silent ?? undefined,
|
|
114
|
+
cfg: ctx.cfg,
|
|
115
|
+
...sendContext.formatting,
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
send: async ({ text, mediaUrl, isFirst }) =>
|
|
119
|
+
await sendContext.withRetry(
|
|
120
|
+
async () =>
|
|
121
|
+
await sendContext.send(sendContext.target, text, {
|
|
122
|
+
verbose: false,
|
|
123
|
+
mediaUrl,
|
|
124
|
+
mediaAccess: ctx.mediaAccess,
|
|
125
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
126
|
+
mediaReadFile: ctx.mediaReadFile,
|
|
127
|
+
components: isFirst ? nativeComponents : undefined,
|
|
128
|
+
embeds: isFirst ? embeds : undefined,
|
|
129
|
+
filename: isFirst ? filename : undefined,
|
|
130
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
131
|
+
accountId: ctx.accountId ?? undefined,
|
|
132
|
+
silent: ctx.silent ?? undefined,
|
|
133
|
+
cfg: ctx.cfg,
|
|
134
|
+
...sendContext.formatting,
|
|
135
|
+
}),
|
|
136
|
+
),
|
|
137
|
+
});
|
|
138
|
+
return attachChannelToResult("discord", result);
|
|
139
|
+
}
|
|
140
|
+
return await sendTextMediaPayload({
|
|
141
|
+
channel: "discord",
|
|
142
|
+
ctx: {
|
|
143
|
+
...ctx,
|
|
144
|
+
payload,
|
|
145
|
+
},
|
|
146
|
+
adapter: params.fallbackAdapter,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = await sendPayloadMediaSequenceOrFallback({
|
|
151
|
+
text: payload.text ?? "",
|
|
152
|
+
mediaUrls,
|
|
153
|
+
fallbackResult: {
|
|
154
|
+
messageId: "",
|
|
155
|
+
channelId: sendContext.target,
|
|
156
|
+
receipt: createDiscordSendReceipt({
|
|
157
|
+
platformMessageIds: [],
|
|
158
|
+
channelId: sendContext.target,
|
|
159
|
+
kind: "unknown",
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
sendNoMedia: async () =>
|
|
163
|
+
await sendContext.withRetry(
|
|
164
|
+
async () =>
|
|
165
|
+
await sendDiscordComponentMessageLazy(sendContext.target, componentSpec, {
|
|
166
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
167
|
+
accountId: ctx.accountId ?? undefined,
|
|
168
|
+
silent: ctx.silent ?? undefined,
|
|
169
|
+
cfg: ctx.cfg,
|
|
170
|
+
...sendContext.formatting,
|
|
171
|
+
}),
|
|
172
|
+
),
|
|
173
|
+
send: async ({ text, mediaUrl, isFirst }) => {
|
|
174
|
+
if (isFirst) {
|
|
175
|
+
return await sendContext.withRetry(
|
|
176
|
+
async () =>
|
|
177
|
+
await sendDiscordComponentMessageLazy(sendContext.target, componentSpec, {
|
|
178
|
+
mediaUrl,
|
|
179
|
+
mediaAccess: ctx.mediaAccess,
|
|
180
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
181
|
+
mediaReadFile: ctx.mediaReadFile,
|
|
182
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
183
|
+
accountId: ctx.accountId ?? undefined,
|
|
184
|
+
silent: ctx.silent ?? undefined,
|
|
185
|
+
cfg: ctx.cfg,
|
|
186
|
+
...sendContext.formatting,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return await sendContext.withRetry(
|
|
191
|
+
async () =>
|
|
192
|
+
await sendContext.send(sendContext.target, text, {
|
|
193
|
+
verbose: false,
|
|
194
|
+
mediaUrl,
|
|
195
|
+
mediaAccess: ctx.mediaAccess,
|
|
196
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
197
|
+
mediaReadFile: ctx.mediaReadFile,
|
|
198
|
+
replyTo: sendContext.resolveReplyTo(),
|
|
199
|
+
accountId: ctx.accountId ?? undefined,
|
|
200
|
+
silent: ctx.silent ?? undefined,
|
|
201
|
+
cfg: ctx.cfg,
|
|
202
|
+
...sendContext.formatting,
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
return attachChannelToResult("discord", result);
|
|
208
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AutoBotConfig, ReplyToMode } from "autobot/plugin-sdk/config-contracts";
|
|
2
|
+
import { createReplyToFanout, type ReplyToResolution } from "autobot/plugin-sdk/outbound-runtime";
|
|
3
|
+
import {
|
|
4
|
+
resolveOutboundSendDep,
|
|
5
|
+
type OutboundSendDeps,
|
|
6
|
+
} from "autobot/plugin-sdk/outbound-send-deps";
|
|
7
|
+
import { normalizeOptionalStringifiedId } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
8
|
+
import { withDiscordDeliveryRetry } from "./delivery-retry.js";
|
|
9
|
+
|
|
10
|
+
type DiscordSendRuntime = typeof import("./send.js");
|
|
11
|
+
|
|
12
|
+
export type DiscordSendFn = DiscordSendRuntime["sendMessageDiscord"];
|
|
13
|
+
export type DiscordVoiceSendFn = DiscordSendRuntime["sendVoiceMessageDiscord"];
|
|
14
|
+
type DiscordFormattingOptions = {
|
|
15
|
+
textLimit?: number;
|
|
16
|
+
maxLinesPerMessage?: number;
|
|
17
|
+
tableMode?: NonNullable<Parameters<DiscordSendFn>[2]>["tableMode"];
|
|
18
|
+
chunkMode?: NonNullable<Parameters<DiscordSendFn>[2]>["chunkMode"];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let discordSendRuntimePromise: Promise<DiscordSendRuntime> | undefined;
|
|
22
|
+
|
|
23
|
+
export async function loadDiscordSendRuntime(): Promise<DiscordSendRuntime> {
|
|
24
|
+
discordSendRuntimePromise ??= import("./send.js");
|
|
25
|
+
return await discordSendRuntimePromise;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveDiscordOutboundTarget(params: {
|
|
29
|
+
to: string;
|
|
30
|
+
threadId?: string | number | null;
|
|
31
|
+
}): string {
|
|
32
|
+
if (params.threadId == null) {
|
|
33
|
+
return params.to;
|
|
34
|
+
}
|
|
35
|
+
const threadId = normalizeOptionalStringifiedId(params.threadId) ?? "";
|
|
36
|
+
if (!threadId) {
|
|
37
|
+
return params.to;
|
|
38
|
+
}
|
|
39
|
+
return `channel:${threadId}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveDiscordFormattingOptions(ctx: {
|
|
43
|
+
formatting?: DiscordFormattingOptions;
|
|
44
|
+
}): DiscordFormattingOptions {
|
|
45
|
+
const formatting = ctx.formatting;
|
|
46
|
+
return {
|
|
47
|
+
textLimit: formatting?.textLimit,
|
|
48
|
+
maxLinesPerMessage: formatting?.maxLinesPerMessage,
|
|
49
|
+
tableMode: formatting?.tableMode,
|
|
50
|
+
chunkMode: formatting?.chunkMode,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function createDiscordPayloadSendContext(ctx: {
|
|
55
|
+
cfg: AutoBotConfig;
|
|
56
|
+
to: string;
|
|
57
|
+
accountId?: string | null;
|
|
58
|
+
deps?: OutboundSendDeps;
|
|
59
|
+
replyToId?: string | null;
|
|
60
|
+
replyToIdSource?: ReplyToResolution["source"];
|
|
61
|
+
replyToMode?: ReplyToMode;
|
|
62
|
+
formatting?: DiscordFormattingOptions;
|
|
63
|
+
threadId?: string | number | null;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
target: string;
|
|
66
|
+
formatting: DiscordFormattingOptions;
|
|
67
|
+
resolveReplyTo: () => string | undefined;
|
|
68
|
+
send: DiscordSendFn;
|
|
69
|
+
sendVoice: DiscordVoiceSendFn;
|
|
70
|
+
withRetry: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
71
|
+
}> {
|
|
72
|
+
const runtime = await loadDiscordSendRuntime();
|
|
73
|
+
return {
|
|
74
|
+
target: resolveDiscordOutboundTarget({ to: ctx.to, threadId: ctx.threadId }),
|
|
75
|
+
formatting: resolveDiscordFormattingOptions(ctx),
|
|
76
|
+
resolveReplyTo: createReplyToFanout({
|
|
77
|
+
replyToId: ctx.replyToId,
|
|
78
|
+
replyToIdSource: ctx.replyToIdSource,
|
|
79
|
+
replyToMode: ctx.replyToMode,
|
|
80
|
+
}),
|
|
81
|
+
send: resolveOutboundSendDep<DiscordSendFn>(ctx.deps, "discord") ?? runtime.sendMessageDiscord,
|
|
82
|
+
sendVoice:
|
|
83
|
+
resolveOutboundSendDep<DiscordVoiceSendFn>(ctx.deps, "discordVoice") ??
|
|
84
|
+
runtime.sendVoiceMessageDiscord,
|
|
85
|
+
withRetry: async (fn) =>
|
|
86
|
+
await withDiscordDeliveryRetry({
|
|
87
|
+
cfg: ctx.cfg,
|
|
88
|
+
accountId: ctx.accountId,
|
|
89
|
+
fn,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { buildThreadAwareOutboundSessionRoute } from "autobot/plugin-sdk/channel-core";
|
|
2
|
+
import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
|
|
3
|
+
import { buildOutboundBaseSessionKey, type RoutePeer } from "autobot/plugin-sdk/routing";
|
|
4
|
+
import { parseDiscordTarget } from "./target-parsing.js";
|
|
5
|
+
|
|
6
|
+
export type ResolveDiscordOutboundSessionRouteParams = {
|
|
7
|
+
cfg: AutoBotConfig;
|
|
8
|
+
agentId: string;
|
|
9
|
+
accountId?: string | null;
|
|
10
|
+
target: string;
|
|
11
|
+
resolvedTarget?: { kind: string };
|
|
12
|
+
replyToId?: string | null;
|
|
13
|
+
threadId?: string | number | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function resolveDiscordOutboundSessionRoute(
|
|
17
|
+
params: ResolveDiscordOutboundSessionRouteParams,
|
|
18
|
+
) {
|
|
19
|
+
const parsed = parseDiscordTarget(params.target, {
|
|
20
|
+
defaultKind: resolveDiscordOutboundTargetKindHint(params),
|
|
21
|
+
});
|
|
22
|
+
if (!parsed) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const isDm = parsed.kind === "user";
|
|
26
|
+
const peer: RoutePeer = {
|
|
27
|
+
kind: isDm ? "direct" : "channel",
|
|
28
|
+
id: parsed.id,
|
|
29
|
+
};
|
|
30
|
+
const baseSessionKey = buildOutboundBaseSessionKey({
|
|
31
|
+
cfg: params.cfg,
|
|
32
|
+
agentId: params.agentId,
|
|
33
|
+
channel: "discord",
|
|
34
|
+
accountId: params.accountId,
|
|
35
|
+
peer,
|
|
36
|
+
});
|
|
37
|
+
return buildThreadAwareOutboundSessionRoute({
|
|
38
|
+
route: {
|
|
39
|
+
sessionKey: baseSessionKey,
|
|
40
|
+
baseSessionKey,
|
|
41
|
+
peer,
|
|
42
|
+
chatType: isDm ? ("direct" as const) : ("channel" as const),
|
|
43
|
+
from: isDm ? `discord:${parsed.id}` : `discord:channel:${parsed.id}`,
|
|
44
|
+
to: isDm ? `user:${parsed.id}` : `channel:${parsed.id}`,
|
|
45
|
+
},
|
|
46
|
+
threadId: params.threadId,
|
|
47
|
+
precedence: ["threadId"],
|
|
48
|
+
useSuffix: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveDiscordOutboundTargetKindHint(params: {
|
|
53
|
+
target: string;
|
|
54
|
+
resolvedTarget?: { kind: string };
|
|
55
|
+
}): "user" | "channel" | undefined {
|
|
56
|
+
const resolvedKind = params.resolvedTarget?.kind;
|
|
57
|
+
if (resolvedKind === "user") {
|
|
58
|
+
return "user";
|
|
59
|
+
}
|
|
60
|
+
if (resolvedKind === "group" || resolvedKind === "channel") {
|
|
61
|
+
return "channel";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const target = params.target.trim();
|
|
65
|
+
if (/^channel:/i.test(target)) {
|
|
66
|
+
return "channel";
|
|
67
|
+
}
|
|
68
|
+
if (/^(user:|discord:|@|<@!?)/i.test(target)) {
|
|
69
|
+
return "user";
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
package/src/pluralkit.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { resolveFetch } from "autobot/plugin-sdk/fetch-runtime";
|
|
2
|
+
|
|
3
|
+
const PLURALKIT_API_BASE = "https://api.pluralkit.me/v2";
|
|
4
|
+
|
|
5
|
+
export type DiscordPluralKitConfig = {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
token?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PluralKitSystemInfo = {
|
|
11
|
+
id: string;
|
|
12
|
+
name?: string | null;
|
|
13
|
+
tag?: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type PluralKitMemberInfo = {
|
|
17
|
+
id: string;
|
|
18
|
+
name?: string | null;
|
|
19
|
+
display_name?: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type PluralKitMessageInfo = {
|
|
23
|
+
id: string;
|
|
24
|
+
original?: string | null;
|
|
25
|
+
sender?: string | null;
|
|
26
|
+
system?: PluralKitSystemInfo | null;
|
|
27
|
+
member?: PluralKitMemberInfo | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function fetchPluralKitMessageInfo(params: {
|
|
31
|
+
messageId: string;
|
|
32
|
+
config?: DiscordPluralKitConfig;
|
|
33
|
+
fetcher?: typeof fetch;
|
|
34
|
+
}): Promise<PluralKitMessageInfo | null> {
|
|
35
|
+
if (!params.config?.enabled) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const fetchImpl = resolveFetch(params.fetcher);
|
|
39
|
+
if (!fetchImpl) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const headers: Record<string, string> = {};
|
|
43
|
+
if (params.config.token?.trim()) {
|
|
44
|
+
headers.Authorization = params.config.token.trim();
|
|
45
|
+
}
|
|
46
|
+
const res = await fetchImpl(`${PLURALKIT_API_BASE}/messages/${params.messageId}`, {
|
|
47
|
+
headers,
|
|
48
|
+
});
|
|
49
|
+
if (res.status === 404) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const text = await res.text().catch(() => "");
|
|
54
|
+
const detail = text.trim() ? `: ${text.trim()}` : "";
|
|
55
|
+
throw new Error(`PluralKit API failed (${res.status})${detail}`);
|
|
56
|
+
}
|
|
57
|
+
return (await res.json()) as PluralKitMessageInfo;
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveChannelPreviewStreamMode,
|
|
3
|
+
type StreamingMode,
|
|
4
|
+
} from "autobot/plugin-sdk/channel-streaming";
|
|
5
|
+
|
|
6
|
+
type DiscordPreviewStreamMode = StreamingMode;
|
|
7
|
+
|
|
8
|
+
export function resolveDiscordPreviewStreamMode(
|
|
9
|
+
params: {
|
|
10
|
+
streamMode?: unknown;
|
|
11
|
+
streaming?: unknown;
|
|
12
|
+
} = {},
|
|
13
|
+
): DiscordPreviewStreamMode {
|
|
14
|
+
if (params.streaming === undefined && params.streamMode === undefined) {
|
|
15
|
+
return "progress";
|
|
16
|
+
}
|
|
17
|
+
return resolveChannelPreviewStreamMode(params, "off");
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./probe.js";
|
package/src/probe.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { BaseProbeResult } from "autobot/plugin-sdk/channel-contract";
|
|
2
|
+
import { formatErrorMessage } from "autobot/plugin-sdk/error-runtime";
|
|
3
|
+
import { resolveFetch } from "autobot/plugin-sdk/fetch-runtime";
|
|
4
|
+
import { fetchWithTimeout } from "autobot/plugin-sdk/text-utility-runtime";
|
|
5
|
+
import { DiscordApiError, fetchDiscord } from "./api.js";
|
|
6
|
+
import { normalizeDiscordToken } from "./token.js";
|
|
7
|
+
|
|
8
|
+
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
|
9
|
+
|
|
10
|
+
export type DiscordProbe = BaseProbeResult & {
|
|
11
|
+
status?: number | null;
|
|
12
|
+
elapsedMs: number;
|
|
13
|
+
bot?: { id?: string | null; username?: string | null };
|
|
14
|
+
application?: DiscordApplicationSummary;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DiscordPrivilegedIntentStatus = "enabled" | "limited" | "disabled";
|
|
18
|
+
|
|
19
|
+
export type DiscordPrivilegedIntentsSummary = {
|
|
20
|
+
messageContent: DiscordPrivilegedIntentStatus;
|
|
21
|
+
guildMembers: DiscordPrivilegedIntentStatus;
|
|
22
|
+
presence: DiscordPrivilegedIntentStatus;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type DiscordApplicationSummary = {
|
|
26
|
+
id?: string | null;
|
|
27
|
+
flags?: number | null;
|
|
28
|
+
intents?: DiscordPrivilegedIntentsSummary;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DISCORD_APP_FLAG_GATEWAY_PRESENCE = 1 << 12;
|
|
32
|
+
const DISCORD_APP_FLAG_GATEWAY_PRESENCE_LIMITED = 1 << 13;
|
|
33
|
+
const DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS = 1 << 14;
|
|
34
|
+
const DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15;
|
|
35
|
+
const DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT = 1 << 18;
|
|
36
|
+
const DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT_LIMITED = 1 << 19;
|
|
37
|
+
|
|
38
|
+
async function fetchDiscordApplicationMe(
|
|
39
|
+
token: string,
|
|
40
|
+
timeoutMs: number,
|
|
41
|
+
fetcher: typeof fetch,
|
|
42
|
+
): Promise<{ id?: string; flags?: number } | undefined> {
|
|
43
|
+
try {
|
|
44
|
+
const normalized = normalizeDiscordToken(token, "channels.discord.token");
|
|
45
|
+
if (!normalized) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return await fetchDiscord<{ id?: string; flags?: number }>(
|
|
49
|
+
"/oauth2/applications/@me",
|
|
50
|
+
normalized,
|
|
51
|
+
createDiscordTimeoutFetch(fetcher, timeoutMs),
|
|
52
|
+
{ retry: { attempts: 1 } },
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createDiscordTimeoutFetch(fetcher: typeof fetch, timeoutMs: number): typeof fetch {
|
|
60
|
+
const fetchImpl = getResolvedFetch(fetcher);
|
|
61
|
+
return ((input: RequestInfo | URL, init?: RequestInit) =>
|
|
62
|
+
fetchWithTimeout(
|
|
63
|
+
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url,
|
|
64
|
+
init ?? {},
|
|
65
|
+
timeoutMs,
|
|
66
|
+
fetchImpl,
|
|
67
|
+
)) as typeof fetch;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveDiscordPrivilegedIntentsFromFlags(
|
|
71
|
+
flags: number,
|
|
72
|
+
): DiscordPrivilegedIntentsSummary {
|
|
73
|
+
const resolve = (enabledBit: number, limitedBit: number) => {
|
|
74
|
+
if ((flags & enabledBit) !== 0) {
|
|
75
|
+
return "enabled";
|
|
76
|
+
}
|
|
77
|
+
if ((flags & limitedBit) !== 0) {
|
|
78
|
+
return "limited";
|
|
79
|
+
}
|
|
80
|
+
return "disabled";
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
presence: resolve(DISCORD_APP_FLAG_GATEWAY_PRESENCE, DISCORD_APP_FLAG_GATEWAY_PRESENCE_LIMITED),
|
|
84
|
+
guildMembers: resolve(
|
|
85
|
+
DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS,
|
|
86
|
+
DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS_LIMITED,
|
|
87
|
+
),
|
|
88
|
+
messageContent: resolve(
|
|
89
|
+
DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT,
|
|
90
|
+
DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT_LIMITED,
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function fetchDiscordApplicationSummary(
|
|
96
|
+
token: string,
|
|
97
|
+
timeoutMs: number,
|
|
98
|
+
fetcher: typeof fetch = fetch,
|
|
99
|
+
): Promise<DiscordApplicationSummary | undefined> {
|
|
100
|
+
const json = await fetchDiscordApplicationMe(token, timeoutMs, fetcher);
|
|
101
|
+
if (!json) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const flags =
|
|
105
|
+
typeof json.flags === "number" && Number.isFinite(json.flags) ? json.flags : undefined;
|
|
106
|
+
return {
|
|
107
|
+
id: json.id ?? null,
|
|
108
|
+
flags: flags ?? null,
|
|
109
|
+
intents:
|
|
110
|
+
typeof flags === "number" ? resolveDiscordPrivilegedIntentsFromFlags(flags) : undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getResolvedFetch(fetcher: typeof fetch): typeof fetch {
|
|
115
|
+
const fetchImpl = resolveFetch(fetcher);
|
|
116
|
+
if (!fetchImpl) {
|
|
117
|
+
throw new Error("fetch is not available");
|
|
118
|
+
}
|
|
119
|
+
return fetchImpl;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function probeDiscord(
|
|
123
|
+
token: string,
|
|
124
|
+
timeoutMs: number,
|
|
125
|
+
opts?: { fetcher?: typeof fetch; includeApplication?: boolean },
|
|
126
|
+
): Promise<DiscordProbe> {
|
|
127
|
+
const started = Date.now();
|
|
128
|
+
const fetcher = opts?.fetcher ?? fetch;
|
|
129
|
+
const includeApplication = opts?.includeApplication === true;
|
|
130
|
+
const normalized = normalizeDiscordToken(token, "channels.discord.token");
|
|
131
|
+
const result: DiscordProbe = {
|
|
132
|
+
ok: false,
|
|
133
|
+
status: null,
|
|
134
|
+
error: null,
|
|
135
|
+
elapsedMs: 0,
|
|
136
|
+
};
|
|
137
|
+
if (!normalized) {
|
|
138
|
+
return {
|
|
139
|
+
...result,
|
|
140
|
+
error: "missing token",
|
|
141
|
+
elapsedMs: Date.now() - started,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const res = await fetchWithTimeout(
|
|
146
|
+
`${DISCORD_API_BASE}/users/@me`,
|
|
147
|
+
{ headers: { Authorization: `Bot ${normalized}` } },
|
|
148
|
+
timeoutMs,
|
|
149
|
+
getResolvedFetch(fetcher),
|
|
150
|
+
);
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
result.status = res.status;
|
|
153
|
+
result.error = `getMe failed (${res.status})`;
|
|
154
|
+
return { ...result, elapsedMs: Date.now() - started };
|
|
155
|
+
}
|
|
156
|
+
const json = (await res.json()) as { id?: string; username?: string };
|
|
157
|
+
result.ok = true;
|
|
158
|
+
result.bot = {
|
|
159
|
+
id: json.id ?? null,
|
|
160
|
+
username: json.username ?? null,
|
|
161
|
+
};
|
|
162
|
+
if (includeApplication) {
|
|
163
|
+
result.application =
|
|
164
|
+
(await fetchDiscordApplicationSummary(normalized, timeoutMs, fetcher)) ?? undefined;
|
|
165
|
+
}
|
|
166
|
+
return { ...result, elapsedMs: Date.now() - started };
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return {
|
|
169
|
+
...result,
|
|
170
|
+
status: err instanceof Response ? err.status : result.status,
|
|
171
|
+
error: formatErrorMessage(err),
|
|
172
|
+
elapsedMs: Date.now() - started,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extract the application (bot user) ID from a Discord bot token by
|
|
179
|
+
* base64-decoding the first segment. Discord tokens have the format:
|
|
180
|
+
* base64(user_id) . timestamp . hmac
|
|
181
|
+
* The decoded first segment is the numeric snowflake ID as a plain string,
|
|
182
|
+
* so we keep it as a string to avoid precision loss for IDs that exceed
|
|
183
|
+
* Number.MAX_SAFE_INTEGER.
|
|
184
|
+
*/
|
|
185
|
+
export function parseApplicationIdFromToken(token: string): string | undefined {
|
|
186
|
+
const normalized = normalizeDiscordToken(token, "channels.discord.token");
|
|
187
|
+
if (!normalized) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const firstDot = normalized.indexOf(".");
|
|
191
|
+
if (firstDot <= 0) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const decoded = Buffer.from(normalized.slice(0, firstDot), "base64").toString("utf-8");
|
|
196
|
+
if (/^\d+$/.test(decoded)) {
|
|
197
|
+
return decoded;
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
} catch {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function fetchDiscordApplicationId(
|
|
206
|
+
token: string,
|
|
207
|
+
timeoutMs: number,
|
|
208
|
+
fetcher: typeof fetch = fetch,
|
|
209
|
+
): Promise<string | undefined> {
|
|
210
|
+
const normalized = normalizeDiscordToken(token, "channels.discord.token");
|
|
211
|
+
if (!normalized) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const parsedApplicationId = parseApplicationIdFromToken(token);
|
|
215
|
+
if (parsedApplicationId) {
|
|
216
|
+
return parsedApplicationId;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const json = await fetchDiscord<{ id?: string }>(
|
|
220
|
+
"/oauth2/applications/@me",
|
|
221
|
+
normalized,
|
|
222
|
+
createDiscordTimeoutFetch(fetcher, timeoutMs),
|
|
223
|
+
);
|
|
224
|
+
if (json?.id) {
|
|
225
|
+
return json.id;
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error instanceof DiscordApiError) {
|
|
230
|
+
if (error.status === 429) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|