@effect-uai/discord 0.14.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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/Discord.d.mts +55 -0
- package/dist/Discord.d.mts.map +1 -0
- package/dist/Discord.mjs +196 -0
- package/dist/Discord.mjs.map +1 -0
- package/dist/events-Du1u-kVV.d.mts +172 -0
- package/dist/events-Du1u-kVV.d.mts.map +1 -0
- package/dist/gateway-C_Ae1HYB.d.mts +57 -0
- package/dist/gateway-C_Ae1HYB.d.mts.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/internal/events.d.mts +2 -0
- package/dist/internal/events.mjs +109 -0
- package/dist/internal/events.mjs.map +1 -0
- package/dist/internal/events.test.d.mts +1 -0
- package/dist/internal/events.test.mjs +130 -0
- package/dist/internal/events.test.mjs.map +1 -0
- package/dist/internal/gateway.d.mts +2 -0
- package/dist/internal/gateway.mjs +216 -0
- package/dist/internal/gateway.mjs.map +1 -0
- package/dist/internal/gateway.test.d.mts +1 -0
- package/dist/internal/gateway.test.mjs +40 -0
- package/dist/internal/gateway.test.mjs.map +1 -0
- package/dist/internal/rest.d.mts +2 -0
- package/dist/internal/rest.mjs +89 -0
- package/dist/internal/rest.mjs.map +1 -0
- package/dist/magic-string.es-CUAnjKYC.mjs +1014 -0
- package/dist/magic-string.es-CUAnjKYC.mjs.map +1 -0
- package/dist/rest-CZSW-VJK.d.mts +44 -0
- package/dist/rest-CZSW-VJK.d.mts.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs +13658 -0
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +1 -0
- package/package.json +59 -0
- package/src/Discord.ts +393 -0
- package/src/index.ts +1 -0
- package/src/internal/events.test.ts +89 -0
- package/src/internal/events.ts +197 -0
- package/src/internal/gateway.test.ts +27 -0
- package/src/internal/gateway.ts +368 -0
- package/src/internal/rest.ts +164 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Data, Duration, Effect, Redacted, Schema } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import * as MessengerError from "@effect-uai/core/MessengerError";
|
|
4
|
+
import * as Multipart from "@effect-uai/core/Multipart";
|
|
5
|
+
//#region src/internal/rest.ts
|
|
6
|
+
const provider = "discord";
|
|
7
|
+
/** Tracks the package version; Discord requires a `DiscordBot (url, version)` agent. */
|
|
8
|
+
const userAgent = "DiscordBot (https://effect-uai.betalyra.com, 0.14.0)";
|
|
9
|
+
/**
|
|
10
|
+
* A REST call that came back non-2xx or never came back at all. `code` is
|
|
11
|
+
* Discord's own JSON error code, `status` the HTTP one; each verb maps this
|
|
12
|
+
* onto its own `MessengerError`.
|
|
13
|
+
*/
|
|
14
|
+
var ApiFailure = class extends Data.TaggedError("DiscordApiFailure") {};
|
|
15
|
+
const ErrorBody = Schema.Struct({
|
|
16
|
+
message: Schema.optional(Schema.String),
|
|
17
|
+
code: Schema.optional(Schema.Number),
|
|
18
|
+
retry_after: Schema.optional(Schema.Number)
|
|
19
|
+
});
|
|
20
|
+
const endpoint = (cfg, path) => `${cfg.baseUrl ?? "https://discord.com/api/v10"}${path}`;
|
|
21
|
+
const constructors = {
|
|
22
|
+
GET: HttpClientRequest.get,
|
|
23
|
+
POST: HttpClientRequest.post,
|
|
24
|
+
PATCH: HttpClientRequest.patch,
|
|
25
|
+
PUT: HttpClientRequest.put,
|
|
26
|
+
DELETE: HttpClientRequest.delete
|
|
27
|
+
};
|
|
28
|
+
const request = (cfg, method, path) => constructors[method](endpoint(cfg, path)).pipe(HttpClientRequest.setHeaders({
|
|
29
|
+
Authorization: `Bot ${Redacted.value(cfg.token)}`,
|
|
30
|
+
"User-Agent": userAgent
|
|
31
|
+
}));
|
|
32
|
+
const transportFailure = (route) => (raw) => new ApiFailure({
|
|
33
|
+
route,
|
|
34
|
+
message: "transport failure",
|
|
35
|
+
raw
|
|
36
|
+
});
|
|
37
|
+
const rejected = (route, status, raw) => Effect.gen(function* () {
|
|
38
|
+
const body = yield* Schema.decodeUnknownEffect(ErrorBody)(raw).pipe(Effect.orElseSucceed(() => ({})));
|
|
39
|
+
return yield* status === 429 && body.retry_after !== void 0 ? new MessengerError.MessengerRateLimited({
|
|
40
|
+
provider,
|
|
41
|
+
retryAfter: Duration.seconds(body.retry_after),
|
|
42
|
+
raw
|
|
43
|
+
}) : new ApiFailure({
|
|
44
|
+
route,
|
|
45
|
+
status,
|
|
46
|
+
...body.code !== void 0 && { code: body.code },
|
|
47
|
+
message: body.message ?? `HTTP ${status}`,
|
|
48
|
+
raw
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const send = (route, httpRequest) => Effect.gen(function* () {
|
|
52
|
+
const response = yield* (yield* HttpClient.HttpClient).execute(httpRequest).pipe(Effect.mapError(transportFailure(route)));
|
|
53
|
+
if (response.status === 204) return void 0;
|
|
54
|
+
const body = yield* response.json.pipe(Effect.mapError(transportFailure(route)));
|
|
55
|
+
return response.status < 400 ? body : yield* rejected(route, response.status, body);
|
|
56
|
+
});
|
|
57
|
+
/** One JSON call against the API base. The response body comes back undecoded. */
|
|
58
|
+
const call = (cfg) => (method, path, body) => send(`${method} ${path}`, body === void 0 ? request(cfg, method, path) : request(cfg, method, path).pipe(HttpClientRequest.bodyJsonUnsafe(body)));
|
|
59
|
+
const formData = (payload, file) => {
|
|
60
|
+
const form = new FormData();
|
|
61
|
+
form.append("payload_json", JSON.stringify({
|
|
62
|
+
...payload,
|
|
63
|
+
attachments: [{
|
|
64
|
+
id: 0,
|
|
65
|
+
filename: file.filename
|
|
66
|
+
}]
|
|
67
|
+
}));
|
|
68
|
+
form.append("files[0]", new File([file.bytes], file.filename, { type: file.mimeType }));
|
|
69
|
+
return form;
|
|
70
|
+
};
|
|
71
|
+
/** One multipart message: the JSON fields as `payload_json` plus a single file. */
|
|
72
|
+
const upload = (cfg) => (path, payload, file) => Effect.gen(function* () {
|
|
73
|
+
const route = `POST ${path}`;
|
|
74
|
+
const body = yield* Multipart.bodyMultipart(formData(payload, file)).pipe(Effect.mapError(transportFailure(route)));
|
|
75
|
+
return yield* send(route, request(cfg, "POST", path).pipe(body));
|
|
76
|
+
});
|
|
77
|
+
/** Decode a response body; a shape mismatch is a malformed response, not a defect. */
|
|
78
|
+
const decoded = (route, schema) => (result) => Effect.gen(function* () {
|
|
79
|
+
const raw = yield* result;
|
|
80
|
+
return yield* Schema.decodeUnknownEffect(schema)(raw).pipe(Effect.mapError(() => new ApiFailure({
|
|
81
|
+
route,
|
|
82
|
+
message: "malformed response",
|
|
83
|
+
raw
|
|
84
|
+
})));
|
|
85
|
+
});
|
|
86
|
+
//#endregion
|
|
87
|
+
export { ApiFailure, call, decoded, provider, upload };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=rest.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.mjs","names":[],"sources":["../../src/internal/rest.ts"],"sourcesContent":["import { Data, Duration, Effect, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Multipart from \"@effect-uai/core/Multipart\"\n\nexport const provider = \"discord\"\n\n/** Tracks the package version; Discord requires a `DiscordBot (url, version)` agent. */\nconst userAgent = \"DiscordBot (https://effect-uai.betalyra.com, 0.14.0)\"\n\nexport type Config = {\n readonly token: Redacted.Redacted\n /** Defaults to `https://discord.com/api/v10`. Always version-pinned. */\n readonly baseUrl?: string\n}\n\nexport type Method = \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\"\n\n/** JSON body fields of a request. */\nexport type Fields = Readonly<Record<string, unknown>>\n\n/**\n * A REST call that came back non-2xx or never came back at all. `code` is\n * Discord's own JSON error code, `status` the HTTP one; each verb maps this\n * onto its own `MessengerError`.\n */\nexport class ApiFailure extends Data.TaggedError(\"DiscordApiFailure\")<{\n readonly route: string\n readonly status?: number\n readonly code?: number\n readonly message: string\n readonly raw: unknown\n}> {}\n\nexport type ApiError = ApiFailure | MessengerError.MessengerRateLimited\n\nconst ErrorBody = Schema.Struct({\n message: Schema.optional(Schema.String),\n code: Schema.optional(Schema.Number),\n retry_after: Schema.optional(Schema.Number),\n})\n\n/** The file part of a multipart message. Discord names it `files[n]`. */\nexport type Upload = {\n readonly bytes: Uint8Array\n readonly filename: string\n readonly mimeType: string\n}\n\nconst endpoint = (cfg: Config, path: string): string =>\n `${cfg.baseUrl ?? \"https://discord.com/api/v10\"}${path}`\n\nconst constructors: Record<Method, (url: string) => HttpClientRequest.HttpClientRequest> = {\n GET: HttpClientRequest.get,\n POST: HttpClientRequest.post,\n PATCH: HttpClientRequest.patch,\n PUT: HttpClientRequest.put,\n DELETE: HttpClientRequest.delete,\n}\n\nconst request = (cfg: Config, method: Method, path: string) =>\n constructors[method](endpoint(cfg, path)).pipe(\n HttpClientRequest.setHeaders({\n Authorization: `Bot ${Redacted.value(cfg.token)}`,\n \"User-Agent\": userAgent,\n }),\n )\n\nconst transportFailure = (route: string) => (raw: unknown) =>\n new ApiFailure({ route, message: \"transport failure\", raw })\n\nconst rejected = (route: string, status: number, raw: unknown): Effect.Effect<never, ApiError> =>\n Effect.gen(function* () {\n const body = yield* Schema.decodeUnknownEffect(ErrorBody)(raw).pipe(\n Effect.orElseSucceed((): typeof ErrorBody.Type => ({})),\n )\n // Discord's `retry_after` is float seconds; only a 429 carries one.\n return yield* status === 429 && body.retry_after !== undefined\n ? new MessengerError.MessengerRateLimited({\n provider,\n retryAfter: Duration.seconds(body.retry_after),\n raw,\n })\n : new ApiFailure({\n route,\n status,\n ...(body.code !== undefined && { code: body.code }),\n message: body.message ?? `HTTP ${status}`,\n raw,\n })\n })\n\nconst send = (\n route: string,\n httpRequest: HttpClientRequest.HttpClientRequest,\n): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(transportFailure(route)))\n // 204 is the success shape of `typing`, `react` and every delete: no body\n // to read, and reading one would fail.\n if (response.status === 204) return undefined\n const body = yield* response.json.pipe(Effect.mapError(transportFailure(route)))\n return response.status < 400 ? body : yield* rejected(route, response.status, body)\n })\n\n/** One JSON call against the API base. The response body comes back undecoded. */\nexport const call =\n (cfg: Config) =>\n (\n method: Method,\n path: string,\n body?: unknown,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(\n `${method} ${path}`,\n body === undefined\n ? request(cfg, method, path)\n : request(cfg, method, path).pipe(HttpClientRequest.bodyJsonUnsafe(body)),\n )\n\n// The `attachments` entry is what binds `files[0]` to the message; without it\n// Discord can drop the part and reject the rest as an empty message.\nconst formData = (payload: Fields, file: Upload): FormData => {\n const form = new FormData()\n form.append(\n \"payload_json\",\n JSON.stringify({ ...payload, attachments: [{ id: 0, filename: file.filename }] }),\n )\n form.append(\n \"files[0]\",\n new File([file.bytes as Uint8Array<ArrayBuffer>], file.filename, { type: file.mimeType }),\n )\n return form\n}\n\n/** One multipart message: the JSON fields as `payload_json` plus a single file. */\nexport const upload =\n (cfg: Config) =>\n (\n path: string,\n payload: Fields,\n file: Upload,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const route = `POST ${path}`\n const body = yield* Multipart.bodyMultipart(formData(payload, file)).pipe(\n Effect.mapError(transportFailure(route)),\n )\n return yield* send(route, request(cfg, \"POST\", path).pipe(body))\n })\n\n/** Decode a response body; a shape mismatch is a malformed response, not a defect. */\nexport const decoded =\n <A>(route: string, schema: Schema.Decoder<A>) =>\n <E, R>(result: Effect.Effect<unknown, E, R>): Effect.Effect<A, E | ApiFailure, R> =>\n Effect.gen(function* () {\n const raw = yield* result\n return yield* Schema.decodeUnknownEffect(schema)(raw).pipe(\n Effect.mapError(() => new ApiFailure({ route, message: \"malformed response\", raw })),\n )\n })\n"],"mappings":"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"}
|