@effect-uai/discord 0.16.0 → 0.18.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/README.md +1 -1
- package/dist/Discord.d.mts +3 -3
- package/dist/Discord.d.mts.map +1 -1
- package/dist/Discord.mjs +7 -5
- package/dist/Discord.mjs.map +1 -1
- package/dist/dist-DHer1cql.mjs +1533 -0
- package/dist/dist-DHer1cql.mjs.map +1 -0
- package/dist/{gateway-C_Ae1HYB.d.mts → gateway-CYyZYm4m.d.mts} +3 -2
- package/dist/gateway-CYyZYm4m.d.mts.map +1 -0
- package/dist/{test.DNmyFkvJ-N3QqMRwZ.mjs → index.C4tKZ0yW-CUBm3Bmz.mjs} +9446 -5818
- package/dist/index.C4tKZ0yW-CUBm3Bmz.mjs.map +1 -0
- package/dist/internal/events.test.mjs +1 -1
- package/dist/internal/gateway.d.mts +1 -1
- package/dist/internal/gateway.mjs +23 -14
- package/dist/internal/gateway.mjs.map +1 -1
- package/dist/internal/gateway.test.mjs +259 -7
- package/dist/internal/gateway.test.mjs.map +1 -1
- package/dist/internal/rest.d.mts +1 -1
- package/dist/internal/rest.mjs +1 -1
- package/dist/internal/rest.mjs.map +1 -1
- package/dist/{rest-CZSW-VJK.d.mts → rest-SmIBt9Nh.d.mts} +2 -2
- package/dist/{rest-CZSW-VJK.d.mts.map → rest-SmIBt9Nh.d.mts.map} +1 -1
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-EAsCxrDo.mjs} +2 -1
- package/package.json +4 -4
- package/src/Discord.ts +7 -4
- package/src/internal/gateway.test.ts +87 -1
- package/src/internal/gateway.ts +32 -22
- package/src/internal/rest.ts +1 -1
- package/dist/gateway-C_Ae1HYB.d.mts.map +0 -1
- package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
- package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ websocket, so no public URL, and no discord.js.
|
|
|
13
13
|
pnpm add @effect-uai/discord @effect-uai/core effect
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
ESM-only. Requires `effect
|
|
16
|
+
ESM-only. Requires `effect@>=4.0.1` and `@effect-uai/core` as peers.
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
package/dist/Discord.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as BotIdentity } from "./events-Du1u-kVV.mjs";
|
|
2
|
-
import { r as Config$1 } from "./rest-
|
|
3
|
-
import { s as classifyClose, t as CloseAction } from "./gateway-
|
|
2
|
+
import { r as Config$1 } from "./rest-SmIBt9Nh.mjs";
|
|
3
|
+
import { s as classifyClose, t as CloseAction } from "./gateway-CYyZYm4m.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Scope } from "effect";
|
|
5
|
-
import { HttpClient } from "effect/
|
|
5
|
+
import { HttpClient } from "effect/http";
|
|
6
6
|
import { Messenger, MessengerLimits, MessengerService } from "@effect-uai/core/Messenger";
|
|
7
7
|
import { StreamViaEditsOptions } from "@effect-uai/core/MessengerAdapter";
|
|
8
8
|
import * as MessengerError from "@effect-uai/core/MessengerError";
|
package/dist/Discord.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Discord.d.mts","names":[],"sources":["../src/Discord.ts"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Discord.d.mts","names":[],"sources":["../src/Discord.ts"],"mappings":";;;;;;;;;;;;cA+Ca;WACX;WACA;WACA;WACA;WACA;WACA;;;;;;;cAQW;KAOD,SAAS;;WAEV;;WAEA,SAAS;;KAGR,iBAAiB;;WAElB,KAAK;;;;;;;cAOH,gBAAgB;cAIhB,QAAQ;cAwFR,OAAI,KACV,WACJ,OAAO,OACR,gBACA,eAAe,wBACf,WAAW,aAAa,MAAM;;;;;;;;cA+MnB,QAAK,KACX,WACJ,MAAM,MAAM,UAAU,WAAW,eAAe,wBAAwB,WAAW"}
|
package/dist/Discord.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { toEvents } from "./internal/events.mjs";
|
|
3
3
|
import { call, decoded, provider, upload } from "./internal/rest.mjs";
|
|
4
4
|
import { GatewayInfo, classifyClose, connect } from "./internal/gateway.mjs";
|
|
5
|
-
import { Array, Context, Effect,
|
|
6
|
-
import {
|
|
5
|
+
import { Array, Context, Effect, Layer, Match, Option, Queue, Schedule, Schema, Stream } from "effect";
|
|
6
|
+
import { Base64 } from "effect/encoding";
|
|
7
|
+
import { HttpClient } from "effect/http";
|
|
8
|
+
import * as Socket from "effect/socket/Socket";
|
|
7
9
|
import { CurrentConversation, MessageId, Messenger } from "@effect-uai/core/Messenger";
|
|
8
10
|
import { splitForLimit, streamViaEdits } from "@effect-uai/core/MessengerAdapter";
|
|
9
11
|
import * as MessengerError from "@effect-uai/core/MessengerError";
|
|
@@ -104,7 +106,7 @@ const make = (cfg) => Effect.gen(function* () {
|
|
|
104
106
|
token: cfg.token,
|
|
105
107
|
intents: cfg.intents ?? defaultIntents,
|
|
106
108
|
url
|
|
107
|
-
});
|
|
109
|
+
}).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal));
|
|
108
110
|
const toEvents$1 = toEvents(session.bot);
|
|
109
111
|
const inbox = yield* Queue.unbounded();
|
|
110
112
|
const acknowledge = (dispatch) => Match.value(dispatch).pipe(Match.when({ t: "INTERACTION_CREATE" }, ({ d }) => d.type === 3 ? call$1("POST", `/interactions/${d.id}/${d.token}/callback`, { type: DEFERRED_UPDATE }).pipe(Effect.ignore) : Effect.void), Match.orElse(() => Effect.void));
|
|
@@ -135,7 +137,7 @@ const make = (cfg) => Effect.gen(function* () {
|
|
|
135
137
|
filename: body.filename ?? defaultFilename(body.media),
|
|
136
138
|
mimeType
|
|
137
139
|
});
|
|
138
|
-
return Match.value(body.media).pipe(Match.tag("url", ({ url }) => sendUrl(at, fields, body, url)), Match.tag("bytes", ({ bytes, mimeType }) => send(bytes, mimeType)), Match.tag("base64", ({ base64, mimeType }) => Effect.fromResult(
|
|
140
|
+
return Match.value(body.media).pipe(Match.tag("url", ({ url }) => sendUrl(at, fields, body, url)), Match.tag("bytes", ({ bytes, mimeType }) => send(bytes, mimeType)), Match.tag("base64", ({ base64, mimeType }) => Effect.fromResult(Base64.decode(base64)).pipe(Effect.mapError(invalidMedia), Effect.flatMap((bytes) => send(bytes, mimeType)))), Match.exhaustive);
|
|
139
141
|
};
|
|
140
142
|
const sendChunks = (at, msg, text) => Effect.gen(function* () {
|
|
141
143
|
const [first, ...rest] = splitForLimit(text, limits.maxText);
|
package/dist/Discord.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Discord.mjs","names":["Rest.provider","call","Rest.call","upload","Rest.upload","Rest.decoded","Gateway.GatewayInfo","Gateway.connect","toEvents","Events.toEvents","Arr"],"sources":["../src/Discord.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Context,\n Effect,\n Encoding,\n Layer,\n Match,\n Option,\n Queue,\n Schedule,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport { HttpClient } from \"effect/unstable/http\"\nimport type { MediaSource } from \"@effect-uai/core/Media\"\nimport {\n CurrentConversation,\n type ConversationRef,\n type InboundEvent,\n MessageId,\n Messenger,\n type MessengerLimits,\n type MessengerService,\n type Outbound,\n type OutboundBody,\n} from \"@effect-uai/core/Messenger\"\nimport {\n type StreamViaEditsOptions,\n splitForLimit,\n streamViaEdits,\n} from \"@effect-uai/core/MessengerAdapter\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Events from \"./internal/events.js\"\nimport * as Gateway from \"./internal/gateway.js\"\nimport * as Rest from \"./internal/rest.js\"\n\nexport type { BotIdentity } from \"./internal/events.js\"\nexport { classifyClose, type CloseAction } from \"./internal/gateway.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Gateway intent bits. `MessageContent` is the one Discord gates behind review. */\nexport const Intents = {\n Guilds: 1 << 0,\n GuildMessages: 1 << 9,\n GuildMessageReactions: 1 << 10,\n DirectMessages: 1 << 12,\n DirectMessageReactions: 1 << 13,\n MessageContent: 1 << 15,\n} as const\n\n/**\n * What a mention-or-DM bot needs. Even without the privileged\n * `MessageContent` intent, `content` arrives for DMs and for messages that\n * mention the bot, which is exactly the set `addressed` covers.\n */\nexport const defaultIntents: number =\n Intents.Guilds |\n Intents.GuildMessages |\n Intents.GuildMessageReactions |\n Intents.DirectMessages |\n Intents.DirectMessageReactions\n\nexport type Config = Rest.Config & {\n /** Gateway intents mask. Defaults to {@link defaultIntents}. */\n readonly intents?: number\n /** Tuning for `stream`, which is post-then-edit on Discord. */\n readonly stream?: StreamViaEditsOptions\n}\n\nexport type DiscordService = MessengerService & {\n /** From `READY` at layer build: the identity behind `addressed`. */\n readonly bot: Events.BotIdentity\n}\n\n/**\n * Provider-typed tag. Yield this for the bot identity; yield the generic\n * `Messenger` tag for provider-portable code. Both are registered by {@link layer}.\n */\nexport class Discord extends Context.Service<Discord, DiscordService>()(\n \"@betalyra/effect-uai/providers/discord/Discord\",\n) {}\n\nexport const limits: MessengerLimits = { maxText: 2000, maxCaption: 2000 }\n\n// Under the observed five-edits-per-five-seconds-per-channel bucket.\nconst defaultStream: StreamViaEditsOptions = { every: \"1200 millis\" }\n\n// A deferred update: the interaction is answered, nothing changes on screen.\nconst DEFERRED_UPDATE = 6\n\n// ---------------------------------------------------------------------------\n// Wire results\n// ---------------------------------------------------------------------------\n\nconst Me = Schema.Struct({ id: Schema.String, username: Schema.String })\nconst Sent = Schema.Struct({ id: Schema.String })\nconst RawCall = Schema.Struct({\n method: Schema.Literals([\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"]),\n path: Schema.String,\n body: Schema.optional(Schema.Unknown),\n})\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nconst isApiFailure = (e: Rest.ApiError): e is Rest.ApiFailure => e._tag === \"DiscordApiFailure\"\n\nconst describes = (needle: string) => (e: Rest.ApiError) =>\n isApiFailure(e) && e.message.includes(needle)\n\nconst reasonOf = (e: Rest.ApiError): string => (isApiFailure(e) ? e.message : \"rate limited\")\n\n/** An API rejection becomes the verb's `MessengerRequestFailed`; typed errors pass through. */\nconst requestFailed =\n (operation: MessengerError.MessengerOperation) =>\n (e: Rest.ApiError | MessengerError.MessengerError): MessengerError.MessengerError =>\n Match.value(e).pipe(\n Match.tag(\n \"DiscordApiFailure\",\n (failure) =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation,\n reason: failure.message,\n raw: failure.raw,\n }),\n ),\n Match.orElse((typed) => typed),\n )\n\nconst unsupported = (capability: string, reason: string) =>\n new MessengerError.MessengerUnsupported({ provider: Rest.provider, capability, reason })\n\n// ---------------------------------------------------------------------------\n// Outbound params\n// ---------------------------------------------------------------------------\n\ntype Fields = Rest.Fields\n\ntype MediaFields = Extract<OutboundBody, { _tag: \"Media\" }>\n\n// Model output must never be able to ping anyone, so nothing is ever parsed.\nconst noMentions: Fields = { allowed_mentions: { parse: [] } }\n\nconst replyFields = (msg: Outbound): Fields =>\n msg.replyTo === undefined\n ? {}\n : { message_reference: { message_id: msg.replyTo, fail_if_not_exists: false } }\n\nconst messages = (at: ConversationRef): string => `/channels/${at.channel}/messages`\n\nconst captionField = (caption: string | undefined): Fields =>\n caption === undefined ? {} : { content: caption }\n\nconst defaultFilename = (media: MediaSource): string =>\n `file.${media.mimeType?.split(\"/\")[1] ?? \"bin\"}`\n\nconst invalidMedia = (raw: unknown) =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation: \"post\",\n reason: \"invalid base64 media\",\n raw,\n })\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<\n DiscordService,\n MessengerError.MessengerConnectFailed,\n HttpClient.HttpClient | Scope.Scope\n> =>\n Effect.gen(function* () {\n // Captured once so the verbs need no `HttpClient` of their own.\n const client = yield* HttpClient.HttpClient\n const withClient = Effect.provideService(HttpClient.HttpClient, client)\n const call = (method: Rest.Method, path: string, body?: unknown) =>\n withClient(Rest.call(cfg)(method, path, body))\n const upload = (path: string, payload: Fields, file: Rest.Upload) =>\n withClient(Rest.upload(cfg)(path, payload, file))\n\n const connectFailed = (e: Rest.ApiError) =>\n new MessengerError.MessengerConnectFailed({\n provider: Rest.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n })\n\n // A bad token fails here rather than as a gateway close a moment later.\n yield* call(\"GET\", \"/users/@me\").pipe(\n Rest.decoded(\"GET /users/@me\", Me),\n Effect.mapError(connectFailed),\n )\n const { url } = yield* call(\"GET\", \"/gateway/bot\").pipe(\n Rest.decoded(\"GET /gateway/bot\", Gateway.GatewayInfo),\n Effect.mapError(connectFailed),\n )\n\n // -- inbound -----------------------------------------------------------\n\n const session = yield* Gateway.connect({\n token: cfg.token,\n intents: cfg.intents ?? defaultIntents,\n url,\n })\n const toEvents = Events.toEvents(session.bot)\n const inbox = yield* Queue.unbounded<InboundEvent, MessengerError.MessengerError | Cause.Done>()\n\n // Discord drops a component interaction that goes unanswered for three\n // seconds, so it is acknowledged in the reader fiber rather than by the\n // recipe, the twin of Telegram's `answerCallbackQuery`.\n const acknowledge = (dispatch: Events.Dispatch) =>\n Match.value(dispatch).pipe(\n Match.when({ t: \"INTERACTION_CREATE\" }, ({ d }) =>\n d.type === Events.MESSAGE_COMPONENT\n ? call(\"POST\", `/interactions/${d.id}/${d.token}/callback`, {\n type: DEFERRED_UPDATE,\n }).pipe(Effect.ignore)\n : Effect.void,\n ),\n Match.orElse(() => Effect.void),\n )\n\n yield* Stream.fromQueue(session.dispatches).pipe(\n Stream.runForEach(({ dispatch, raw }) =>\n acknowledge(dispatch).pipe(Effect.andThen(Queue.offerAll(inbox, toEvents(dispatch, raw)))),\n ),\n Effect.catch((closed) => Queue.fail(inbox, closed)),\n Effect.ensuring(Queue.end(inbox)),\n Effect.forkScoped,\n )\n\n // -- outbound ----------------------------------------------------------\n\n const sendRaw = (payload: unknown) =>\n Effect.gen(function* () {\n const { method, path, body } = yield* Schema.decodeUnknownEffect(RawCall)(payload).pipe(\n Effect.mapError(\n () =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation: \"post\",\n reason: \"raw payload must be { method, path, body? }\",\n raw: payload,\n }),\n ),\n )\n return yield* call(method, path, body)\n })\n\n // Discord has no attachment-by-URL, so a URL goes as content and Discord\n // unfurls it. Not as an embed: embeds need the Embed Links permission and\n // are silently stripped without it, which Discord then rejects as an\n // empty message.\n const sendUrl = (at: ConversationRef, fields: Fields, body: MediaFields, url: string) =>\n call(\"POST\", messages(at), {\n ...fields,\n content: Arr.getSomes([Option.fromNullishOr(body.caption), Option.some(url)]).join(\"\\n\"),\n })\n\n const sendMedia = (at: ConversationRef, msg: Outbound, body: MediaFields) => {\n const fields = { ...noMentions, ...replyFields(msg) }\n const send = (bytes: Uint8Array, mimeType: string) =>\n upload(\n messages(at),\n { ...fields, ...captionField(body.caption) },\n { bytes, filename: body.filename ?? defaultFilename(body.media), mimeType },\n )\n return Match.value(body.media).pipe(\n Match.tag(\"url\", ({ url }) => sendUrl(at, fields, body, url)),\n Match.tag(\"bytes\", ({ bytes, mimeType }) => send(bytes, mimeType)),\n Match.tag(\"base64\", ({ base64, mimeType }) =>\n Effect.fromResult(Encoding.decodeBase64(base64)).pipe(\n Effect.mapError(invalidMedia),\n Effect.flatMap((bytes) => send(bytes, mimeType)),\n ),\n ),\n Match.exhaustive,\n )\n }\n\n // The reply lands on the first chunk; the id is the last one's.\n const sendChunks = (at: ConversationRef, msg: Outbound, text: string) =>\n Effect.gen(function* () {\n const [first, ...rest] = splitForLimit(text, limits.maxText)\n const send = (chunk: string, extra: Fields) =>\n call(\"POST\", messages(at), { ...noMentions, ...extra, content: chunk }).pipe(\n Rest.decoded(\"POST message\", Sent),\n )\n const head = yield* send(first, replyFields(msg))\n const tail = yield* Effect.forEach(rest, (chunk) => send(chunk, {}))\n return Option.getOrElse(Arr.last(tail), () => head)\n })\n\n const post: MessengerService[\"post\"] = (msg) =>\n Effect.gen(function* () {\n const at = yield* CurrentConversation\n const sent = yield* Match.value(msg.body).pipe(\n Match.tag(\"Text\", ({ text }) => sendChunks(at, msg, text)),\n Match.tag(\"Media\", (body) =>\n sendMedia(at, msg, body).pipe(Rest.decoded(\"POST media\", Sent)),\n ),\n Match.tag(\"Raw\", ({ payload }) => sendRaw(payload).pipe(Rest.decoded(\"raw\", Sent))),\n Match.exhaustive,\n )\n return MessageId(sent.id)\n }).pipe(Effect.mapError(requestFailed(\"post\")))\n\n const edit: MessengerService[\"edit\"] = (ref, next) =>\n Match.value(next.body).pipe(\n Match.tag(\"Text\", ({ text }) =>\n call(\"PATCH\", `${messages(ref.conversation)}/${ref.id}`, {\n ...noMentions,\n content: text,\n }).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Raw\", ({ payload }) =>\n sendRaw(payload).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Media\", () =>\n Effect.fail(unsupported(\"media edits\", \"send a new message instead\")),\n ),\n Match.exhaustive,\n )\n\n // Unicode emoji go URL-encoded and custom ones as `name:id`, which is the\n // same spelling a `Reaction` event delivers.\n const react: MessengerService[\"react\"] = (ref, emoji) =>\n call(\n \"PUT\",\n `${messages(ref.conversation)}/${ref.id}/reactions/${encodeURIComponent(emoji)}/@me`,\n ).pipe(\n Effect.asVoid,\n Effect.catchIf(describes(\"Unknown Emoji\"), () =>\n Effect.fail(unsupported(\"reaction\", `${emoji} is not an emoji Discord knows`)),\n ),\n Effect.mapError(requestFailed(\"react\")),\n )\n\n // The indicator lasts ten seconds, so it is re-sent every eight until the\n // scope closes. The first send is awaited so a bad channel fails here.\n const typing: MessengerService[\"typing\"] = Effect.gen(function* () {\n const at = yield* CurrentConversation\n const once = call(\"POST\", `/channels/${at.channel}/typing`)\n yield* once.pipe(Effect.mapError(requestFailed(\"typing\")))\n yield* once.pipe(\n Effect.ignore,\n Effect.schedule(Schedule.spaced(\"8 seconds\")),\n Effect.forkScoped,\n )\n })\n\n return {\n bot: session.bot,\n events: Stream.fromQueue(inbox),\n post,\n edit,\n react,\n typing,\n stream: streamViaEdits({ post, edit, limits }, { ...defaultStream, ...cfg.stream }),\n limits,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * One gateway session, registered under both the `Discord` and `Messenger`\n * tags. Building the layer identifies and waits for `READY`, so a bad token\n * or an intent the portal has not granted fails here; the connection lives\n * until the scope closes and `events` ends with it. Run one instance per\n * bot token.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Discord | Messenger, MessengerError.MessengerConnectFailed, HttpClient.HttpClient> =>\n Layer.effectContext(\n Effect.map(make(cfg), (service) =>\n Context.make(Discord, service).pipe(Context.add(Messenger, service)),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Discord.mjs","names":["Rest.provider","call","Rest.call","upload","Rest.upload","Rest.decoded","Gateway.GatewayInfo","Gateway.connect","toEvents","Events.toEvents","Arr"],"sources":["../src/Discord.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Context,\n Effect,\n Layer,\n Match,\n Option,\n Queue,\n Schedule,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { HttpClient } from \"effect/http\"\nimport * as Socket from \"effect/socket/Socket\"\nimport type { MediaSource } from \"@effect-uai/core/Media\"\nimport {\n CurrentConversation,\n type ConversationRef,\n type InboundEvent,\n MessageId,\n Messenger,\n type MessengerLimits,\n type MessengerService,\n type Outbound,\n type OutboundBody,\n} from \"@effect-uai/core/Messenger\"\nimport {\n type StreamViaEditsOptions,\n splitForLimit,\n streamViaEdits,\n} from \"@effect-uai/core/MessengerAdapter\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Events from \"./internal/events.js\"\nimport * as Gateway from \"./internal/gateway.js\"\nimport * as Rest from \"./internal/rest.js\"\n\nexport type { BotIdentity } from \"./internal/events.js\"\nexport { classifyClose, type CloseAction } from \"./internal/gateway.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Gateway intent bits. `MessageContent` is the one Discord gates behind review. */\nexport const Intents = {\n Guilds: 1 << 0,\n GuildMessages: 1 << 9,\n GuildMessageReactions: 1 << 10,\n DirectMessages: 1 << 12,\n DirectMessageReactions: 1 << 13,\n MessageContent: 1 << 15,\n} as const\n\n/**\n * What a mention-or-DM bot needs. Even without the privileged\n * `MessageContent` intent, `content` arrives for DMs and for messages that\n * mention the bot, which is exactly the set `addressed` covers.\n */\nexport const defaultIntents: number =\n Intents.Guilds |\n Intents.GuildMessages |\n Intents.GuildMessageReactions |\n Intents.DirectMessages |\n Intents.DirectMessageReactions\n\nexport type Config = Rest.Config & {\n /** Gateway intents mask. Defaults to {@link defaultIntents}. */\n readonly intents?: number\n /** Tuning for `stream`, which is post-then-edit on Discord. */\n readonly stream?: StreamViaEditsOptions\n}\n\nexport type DiscordService = MessengerService & {\n /** From `READY` at layer build: the identity behind `addressed`. */\n readonly bot: Events.BotIdentity\n}\n\n/**\n * Provider-typed tag. Yield this for the bot identity; yield the generic\n * `Messenger` tag for provider-portable code. Both are registered by {@link layer}.\n */\nexport class Discord extends Context.Service<Discord, DiscordService>()(\n \"@betalyra/effect-uai/providers/discord/Discord\",\n) {}\n\nexport const limits: MessengerLimits = { maxText: 2000, maxCaption: 2000 }\n\n// Under the observed five-edits-per-five-seconds-per-channel bucket.\nconst defaultStream: StreamViaEditsOptions = { every: \"1200 millis\" }\n\n// A deferred update: the interaction is answered, nothing changes on screen.\nconst DEFERRED_UPDATE = 6\n\n// ---------------------------------------------------------------------------\n// Wire results\n// ---------------------------------------------------------------------------\n\nconst Me = Schema.Struct({ id: Schema.String, username: Schema.String })\nconst Sent = Schema.Struct({ id: Schema.String })\nconst RawCall = Schema.Struct({\n method: Schema.Literals([\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"]),\n path: Schema.String,\n body: Schema.optional(Schema.Unknown),\n})\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nconst isApiFailure = (e: Rest.ApiError): e is Rest.ApiFailure => e._tag === \"DiscordApiFailure\"\n\nconst describes = (needle: string) => (e: Rest.ApiError) =>\n isApiFailure(e) && e.message.includes(needle)\n\nconst reasonOf = (e: Rest.ApiError): string => (isApiFailure(e) ? e.message : \"rate limited\")\n\n/** An API rejection becomes the verb's `MessengerRequestFailed`; typed errors pass through. */\nconst requestFailed =\n (operation: MessengerError.MessengerOperation) =>\n (e: Rest.ApiError | MessengerError.MessengerError): MessengerError.MessengerError =>\n Match.value(e).pipe(\n Match.tag(\n \"DiscordApiFailure\",\n (failure) =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation,\n reason: failure.message,\n raw: failure.raw,\n }),\n ),\n Match.orElse((typed) => typed),\n )\n\nconst unsupported = (capability: string, reason: string) =>\n new MessengerError.MessengerUnsupported({ provider: Rest.provider, capability, reason })\n\n// ---------------------------------------------------------------------------\n// Outbound params\n// ---------------------------------------------------------------------------\n\ntype Fields = Rest.Fields\n\ntype MediaFields = Extract<OutboundBody, { _tag: \"Media\" }>\n\n// Model output must never be able to ping anyone, so nothing is ever parsed.\nconst noMentions: Fields = { allowed_mentions: { parse: [] } }\n\nconst replyFields = (msg: Outbound): Fields =>\n msg.replyTo === undefined\n ? {}\n : { message_reference: { message_id: msg.replyTo, fail_if_not_exists: false } }\n\nconst messages = (at: ConversationRef): string => `/channels/${at.channel}/messages`\n\nconst captionField = (caption: string | undefined): Fields =>\n caption === undefined ? {} : { content: caption }\n\nconst defaultFilename = (media: MediaSource): string =>\n `file.${media.mimeType?.split(\"/\")[1] ?? \"bin\"}`\n\nconst invalidMedia = (raw: unknown) =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation: \"post\",\n reason: \"invalid base64 media\",\n raw,\n })\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<\n DiscordService,\n MessengerError.MessengerConnectFailed,\n HttpClient.HttpClient | Scope.Scope\n> =>\n Effect.gen(function* () {\n // Captured once so the verbs need no `HttpClient` of their own.\n const client = yield* HttpClient.HttpClient\n const withClient = Effect.provideService(HttpClient.HttpClient, client)\n const call = (method: Rest.Method, path: string, body?: unknown) =>\n withClient(Rest.call(cfg)(method, path, body))\n const upload = (path: string, payload: Fields, file: Rest.Upload) =>\n withClient(Rest.upload(cfg)(path, payload, file))\n\n const connectFailed = (e: Rest.ApiError) =>\n new MessengerError.MessengerConnectFailed({\n provider: Rest.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n })\n\n // A bad token fails here rather than as a gateway close a moment later.\n yield* call(\"GET\", \"/users/@me\").pipe(\n Rest.decoded(\"GET /users/@me\", Me),\n Effect.mapError(connectFailed),\n )\n const { url } = yield* call(\"GET\", \"/gateway/bot\").pipe(\n Rest.decoded(\"GET /gateway/bot\", Gateway.GatewayInfo),\n Effect.mapError(connectFailed),\n )\n\n // -- inbound -----------------------------------------------------------\n\n // The gateway takes its token in the identify payload, not a header, so\n // every runtime's global `WebSocket` is enough.\n const session = yield* Gateway.connect({\n token: cfg.token,\n intents: cfg.intents ?? defaultIntents,\n url,\n }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal))\n const toEvents = Events.toEvents(session.bot)\n const inbox = yield* Queue.unbounded<InboundEvent, MessengerError.MessengerError | Cause.Done>()\n\n // Discord drops a component interaction that goes unanswered for three\n // seconds, so it is acknowledged in the reader fiber rather than by the\n // recipe, the twin of Telegram's `answerCallbackQuery`.\n const acknowledge = (dispatch: Events.Dispatch) =>\n Match.value(dispatch).pipe(\n Match.when({ t: \"INTERACTION_CREATE\" }, ({ d }) =>\n d.type === Events.MESSAGE_COMPONENT\n ? call(\"POST\", `/interactions/${d.id}/${d.token}/callback`, {\n type: DEFERRED_UPDATE,\n }).pipe(Effect.ignore)\n : Effect.void,\n ),\n Match.orElse(() => Effect.void),\n )\n\n yield* Stream.fromQueue(session.dispatches).pipe(\n Stream.runForEach(({ dispatch, raw }) =>\n acknowledge(dispatch).pipe(Effect.andThen(Queue.offerAll(inbox, toEvents(dispatch, raw)))),\n ),\n Effect.catch((closed) => Queue.fail(inbox, closed)),\n Effect.ensuring(Queue.end(inbox)),\n Effect.forkScoped,\n )\n\n // -- outbound ----------------------------------------------------------\n\n const sendRaw = (payload: unknown) =>\n Effect.gen(function* () {\n const { method, path, body } = yield* Schema.decodeUnknownEffect(RawCall)(payload).pipe(\n Effect.mapError(\n () =>\n new MessengerError.MessengerRequestFailed({\n provider: Rest.provider,\n operation: \"post\",\n reason: \"raw payload must be { method, path, body? }\",\n raw: payload,\n }),\n ),\n )\n return yield* call(method, path, body)\n })\n\n // Discord has no attachment-by-URL, so a URL goes as content and Discord\n // unfurls it. Not as an embed: embeds need the Embed Links permission and\n // are silently stripped without it, which Discord then rejects as an\n // empty message.\n const sendUrl = (at: ConversationRef, fields: Fields, body: MediaFields, url: string) =>\n call(\"POST\", messages(at), {\n ...fields,\n content: Arr.getSomes([Option.fromNullishOr(body.caption), Option.some(url)]).join(\"\\n\"),\n })\n\n const sendMedia = (at: ConversationRef, msg: Outbound, body: MediaFields) => {\n const fields = { ...noMentions, ...replyFields(msg) }\n const send = (bytes: Uint8Array, mimeType: string) =>\n upload(\n messages(at),\n { ...fields, ...captionField(body.caption) },\n { bytes, filename: body.filename ?? defaultFilename(body.media), mimeType },\n )\n return Match.value(body.media).pipe(\n Match.tag(\"url\", ({ url }) => sendUrl(at, fields, body, url)),\n Match.tag(\"bytes\", ({ bytes, mimeType }) => send(bytes, mimeType)),\n Match.tag(\"base64\", ({ base64, mimeType }) =>\n Effect.fromResult(Base64.decode(base64)).pipe(\n Effect.mapError(invalidMedia),\n Effect.flatMap((bytes) => send(bytes, mimeType)),\n ),\n ),\n Match.exhaustive,\n )\n }\n\n // The reply lands on the first chunk; the id is the last one's.\n const sendChunks = (at: ConversationRef, msg: Outbound, text: string) =>\n Effect.gen(function* () {\n const [first, ...rest] = splitForLimit(text, limits.maxText)\n const send = (chunk: string, extra: Fields) =>\n call(\"POST\", messages(at), { ...noMentions, ...extra, content: chunk }).pipe(\n Rest.decoded(\"POST message\", Sent),\n )\n const head = yield* send(first, replyFields(msg))\n const tail = yield* Effect.forEach(rest, (chunk) => send(chunk, {}))\n return Option.getOrElse(Arr.last(tail), () => head)\n })\n\n const post: MessengerService[\"post\"] = (msg) =>\n Effect.gen(function* () {\n const at = yield* CurrentConversation\n const sent = yield* Match.value(msg.body).pipe(\n Match.tag(\"Text\", ({ text }) => sendChunks(at, msg, text)),\n Match.tag(\"Media\", (body) =>\n sendMedia(at, msg, body).pipe(Rest.decoded(\"POST media\", Sent)),\n ),\n Match.tag(\"Raw\", ({ payload }) => sendRaw(payload).pipe(Rest.decoded(\"raw\", Sent))),\n Match.exhaustive,\n )\n return MessageId(sent.id)\n }).pipe(Effect.mapError(requestFailed(\"post\")))\n\n const edit: MessengerService[\"edit\"] = (ref, next) =>\n Match.value(next.body).pipe(\n Match.tag(\"Text\", ({ text }) =>\n call(\"PATCH\", `${messages(ref.conversation)}/${ref.id}`, {\n ...noMentions,\n content: text,\n }).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Raw\", ({ payload }) =>\n sendRaw(payload).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Media\", () =>\n Effect.fail(unsupported(\"media edits\", \"send a new message instead\")),\n ),\n Match.exhaustive,\n )\n\n // Unicode emoji go URL-encoded and custom ones as `name:id`, which is the\n // same spelling a `Reaction` event delivers.\n const react: MessengerService[\"react\"] = (ref, emoji) =>\n call(\n \"PUT\",\n `${messages(ref.conversation)}/${ref.id}/reactions/${encodeURIComponent(emoji)}/@me`,\n ).pipe(\n Effect.asVoid,\n Effect.catchIf(describes(\"Unknown Emoji\"), () =>\n Effect.fail(unsupported(\"reaction\", `${emoji} is not an emoji Discord knows`)),\n ),\n Effect.mapError(requestFailed(\"react\")),\n )\n\n // The indicator lasts ten seconds, so it is re-sent every eight until the\n // scope closes. The first send is awaited so a bad channel fails here.\n const typing: MessengerService[\"typing\"] = Effect.gen(function* () {\n const at = yield* CurrentConversation\n const once = call(\"POST\", `/channels/${at.channel}/typing`)\n yield* once.pipe(Effect.mapError(requestFailed(\"typing\")))\n yield* once.pipe(\n Effect.ignore,\n Effect.schedule(Schedule.spaced(\"8 seconds\")),\n Effect.forkScoped,\n )\n })\n\n return {\n bot: session.bot,\n events: Stream.fromQueue(inbox),\n post,\n edit,\n react,\n typing,\n stream: streamViaEdits({ post, edit, limits }, { ...defaultStream, ...cfg.stream }),\n limits,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * One gateway session, registered under both the `Discord` and `Messenger`\n * tags. Building the layer identifies and waits for `READY`, so a bad token\n * or an intent the portal has not granted fails here; the connection lives\n * until the scope closes and `events` ends with it. Run one instance per\n * bot token.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Discord | Messenger, MessengerError.MessengerConnectFailed, HttpClient.HttpClient> =>\n Layer.effectContext(\n Effect.map(make(cfg), (service) =>\n Context.make(Discord, service).pipe(Context.add(Messenger, service)),\n ),\n )\n"],"mappings":"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"}
|