dfx 0.42.2 → 0.42.3

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.
Files changed (97) hide show
  1. package/Cache/driver.js.map +1 -1
  2. package/Cache/memory.js.map +1 -1
  3. package/Cache/memoryTTL.js.map +1 -1
  4. package/Cache/prelude.js.map +1 -1
  5. package/Cache.js.map +1 -1
  6. package/DiscordConfig.js.map +1 -1
  7. package/DiscordGateway/DiscordWS.js.map +1 -1
  8. package/DiscordGateway/Shard/heartbeats.js.map +1 -1
  9. package/DiscordGateway/Shard/identify.js.map +1 -1
  10. package/DiscordGateway/Shard/invalidSession.js.map +1 -1
  11. package/DiscordGateway/Shard/sendEvents.js.map +1 -1
  12. package/DiscordGateway/Shard/utils.js.map +1 -1
  13. package/DiscordGateway/Shard.js.map +1 -1
  14. package/DiscordGateway/ShardStore.js.map +1 -1
  15. package/DiscordGateway/Sharder.js.map +1 -1
  16. package/DiscordGateway/WS.js.map +1 -1
  17. package/DiscordGateway.js.map +1 -1
  18. package/DiscordREST/types.js.map +1 -1
  19. package/DiscordREST/utils.js.map +1 -1
  20. package/DiscordREST.js.map +1 -1
  21. package/Helpers/flags.js.map +1 -1
  22. package/Helpers/intents.js.map +1 -1
  23. package/Helpers/interactions.js.map +1 -1
  24. package/Helpers/members.js.map +1 -1
  25. package/Helpers/permissions.js.map +1 -1
  26. package/Helpers/ui.js.map +1 -1
  27. package/Interactions/context.js.map +1 -1
  28. package/Interactions/definitions.js.map +1 -1
  29. package/Interactions/gateway.js.map +1 -1
  30. package/Interactions/handlers.js.map +1 -1
  31. package/Interactions/index.js.map +1 -1
  32. package/Interactions/utils.js.map +1 -1
  33. package/Interactions/webhook.js.map +1 -1
  34. package/Log.js.map +1 -1
  35. package/RateLimit/memory.js.map +1 -1
  36. package/RateLimit/utils.js.map +1 -1
  37. package/RateLimit.js.map +1 -1
  38. package/_common.js.map +1 -1
  39. package/gateway.js.map +1 -1
  40. package/global.js.map +1 -1
  41. package/index.js.map +1 -1
  42. package/package.json +50 -52
  43. package/src/Cache/driver.ts +31 -0
  44. package/src/Cache/memory.ts +76 -0
  45. package/src/Cache/memoryTTL.ts +201 -0
  46. package/src/Cache/prelude.ts +215 -0
  47. package/src/Cache.ts +140 -0
  48. package/src/DiscordConfig.ts +48 -0
  49. package/src/DiscordGateway/DiscordWS.ts +74 -0
  50. package/src/DiscordGateway/Shard/heartbeats.ts +42 -0
  51. package/src/DiscordGateway/Shard/identify.ts +52 -0
  52. package/src/DiscordGateway/Shard/invalidSession.ts +10 -0
  53. package/src/DiscordGateway/Shard/sendEvents.ts +37 -0
  54. package/src/DiscordGateway/Shard/utils.ts +14 -0
  55. package/src/DiscordGateway/Shard.ts +152 -0
  56. package/src/DiscordGateway/ShardStore.ts +33 -0
  57. package/src/DiscordGateway/Sharder.ts +102 -0
  58. package/src/DiscordGateway/WS.ts +122 -0
  59. package/src/DiscordGateway.ts +43 -0
  60. package/src/DiscordREST/types.ts +13 -0
  61. package/src/DiscordREST/utils.ts +33 -0
  62. package/src/DiscordREST.ts +203 -0
  63. package/src/Helpers/flags.ts +68 -0
  64. package/src/Helpers/intents.ts +34 -0
  65. package/src/Helpers/interactions.ts +229 -0
  66. package/src/Helpers/members.ts +14 -0
  67. package/src/Helpers/permissions.ts +140 -0
  68. package/src/Helpers/ui.ts +103 -0
  69. package/src/Interactions/context.ts +132 -0
  70. package/src/Interactions/definitions.ts +309 -0
  71. package/src/Interactions/gateway.ts +71 -0
  72. package/src/Interactions/handlers.ts +130 -0
  73. package/src/Interactions/index.ts +108 -0
  74. package/src/Interactions/utils.ts +81 -0
  75. package/src/Interactions/webhook.ts +110 -0
  76. package/src/Log.ts +17 -0
  77. package/src/RateLimit/memory.ts +57 -0
  78. package/src/RateLimit/utils.ts +27 -0
  79. package/src/RateLimit.ts +69 -0
  80. package/src/_common.ts +43 -0
  81. package/src/gateway.ts +38 -0
  82. package/src/global.ts +45 -0
  83. package/src/index.ts +20 -0
  84. package/src/package.json +52 -0
  85. package/src/types.ts +6368 -0
  86. package/src/utils/effect.ts +0 -0
  87. package/src/utils/hub.ts +47 -0
  88. package/src/utils/json.d.ts +1 -0
  89. package/src/utils/tsplus.ts +10 -0
  90. package/src/webhooks.ts +41 -0
  91. package/tsconfig.json +23 -0
  92. package/tsplus.config.json +8 -0
  93. package/types.js.map +1 -1
  94. package/utils/effect.js.map +1 -1
  95. package/utils/hub.js.map +1 -1
  96. package/utils/tsplus.js.map +1 -1
  97. package/webhooks.js.map +1 -1
@@ -0,0 +1,102 @@
1
+ import { millis } from "@effect/data/Duration"
2
+ import { DiscordConfig } from "dfx/DiscordConfig"
3
+ import { DiscordREST } from "dfx/DiscordREST"
4
+ import { LiveRateLimiter, RateLimiter } from "../RateLimit.js"
5
+ import { LiveShard, Shard } from "./Shard.js"
6
+ import { ShardStore } from "./ShardStore.js"
7
+ import { WebSocketCloseError, WebSocketError } from "./WS.js"
8
+
9
+ const make = Do($ => {
10
+ const store = $(ShardStore)
11
+ const rest = $(DiscordREST)
12
+ const { gateway: config } = $(DiscordConfig)
13
+ const limiter = $(RateLimiter)
14
+ const shard = $(Shard)
15
+
16
+ const takeConfig = (totalCount: number) =>
17
+ Do($ => {
18
+ const currentCount = $(Ref.make(0))
19
+
20
+ const claimId = (sharderCount: number): Effect<never, never, number> =>
21
+ store
22
+ .claimId({
23
+ totalCount,
24
+ sharderCount,
25
+ })
26
+ .flatMap(a =>
27
+ a.match(
28
+ () => claimId(sharderCount).delay(Duration.minutes(3)),
29
+ id => Effect.succeed(id),
30
+ ),
31
+ )
32
+
33
+ return currentCount
34
+ .getAndUpdate(_ => _ + 1)
35
+ .flatMap(claimId)
36
+ .map(id => ({ id, totalCount } as const))
37
+ })
38
+
39
+ const gateway = $(
40
+ rest
41
+ .getGatewayBot()
42
+ .flatMap(r => r.json)
43
+ .catchAll(() =>
44
+ Effect.succeed<Discord.GetGatewayBotResponse>({
45
+ url: "wss://gateway.discord.gg/",
46
+ shards: 1,
47
+ session_start_limit: {
48
+ total: 0,
49
+ remaining: 0,
50
+ reset_after: 0,
51
+ max_concurrency: 1,
52
+ },
53
+ }),
54
+ ),
55
+ )
56
+
57
+ const run = (hub: Hub<Discord.GatewayPayload<Discord.ReceiveEvent>>) =>
58
+ Do($ => {
59
+ const deferred = $(
60
+ Deferred.make<WebSocketError | WebSocketCloseError, never>(),
61
+ )
62
+ const take = $(takeConfig(config.shardCount ?? gateway.shards))
63
+
64
+ const spawner = take
65
+ .map(config => ({
66
+ ...config,
67
+ url: gateway.url,
68
+ concurrency: gateway.session_start_limit.max_concurrency,
69
+ }))
70
+ .tap(({ id, concurrency }) =>
71
+ limiter.maybeWait(
72
+ `dfx.sharder.${id % concurrency}`,
73
+ millis(config.identifyRateLimit[0]),
74
+ config.identifyRateLimit[1],
75
+ ),
76
+ )
77
+ .flatMap(c => shard.connect([c.id, c.totalCount], hub))
78
+ .flatMap(
79
+ shard => shard.run.catchAllCause(_ => deferred.failCause(_)).fork,
80
+ ).forever
81
+
82
+ const spawners = Chunk.range(
83
+ 1,
84
+ gateway.session_start_limit.max_concurrency,
85
+ ).map(() => spawner)
86
+
87
+ return $(
88
+ Effect.allParDiscard(spawners).zipParLeft(deferred.await) as Effect<
89
+ never,
90
+ WebSocketError | WebSocketCloseError,
91
+ never
92
+ >,
93
+ )
94
+ })
95
+
96
+ return { run } as const
97
+ })
98
+
99
+ export interface Sharder extends Effect.Success<typeof make> {}
100
+ export const Sharder = Tag<Sharder>()
101
+ export const LiveSharder =
102
+ (LiveRateLimiter + LiveShard) >> make.toLayer(Sharder)
@@ -0,0 +1,122 @@
1
+ import { Log } from "dfx/Log"
2
+ import WebSocket from "isomorphic-ws"
3
+
4
+ export const Reconnect = Symbol()
5
+ export type Reconnect = typeof Reconnect
6
+ export type Message = string | Buffer | ArrayBuffer | Reconnect
7
+
8
+ export class WebSocketError {
9
+ readonly _tag = "WebSocketError"
10
+ constructor(
11
+ readonly reason: "open-timeout" | "error",
12
+ readonly error?: unknown,
13
+ ) {}
14
+ }
15
+
16
+ export class WebSocketCloseError {
17
+ readonly _tag = "WebSocketCloseError"
18
+ constructor(readonly code: number, readonly reason: string) {}
19
+ }
20
+
21
+ const isReconnect = (
22
+ e: WebSocketError | WebSocketCloseError,
23
+ ): e is WebSocketCloseError =>
24
+ e._tag === "WebSocketCloseError" && e.code === 1012
25
+
26
+ const socket = (urlRef: Ref<string>) =>
27
+ urlRef.get
28
+ .map(_ => new WebSocket(_) as any as globalThis.WebSocket)
29
+ .acquireRelease(ws =>
30
+ Effect.sync(() => {
31
+ ;(ws as any).removeAllListeners?.()
32
+ ws.close()
33
+ }),
34
+ )
35
+
36
+ const offer = (
37
+ ws: globalThis.WebSocket,
38
+ queue: Enqueue<WebSocket.Data>,
39
+ log: Log,
40
+ ) =>
41
+ Effect.async<never, WebSocketError | WebSocketCloseError, never>(resume => {
42
+ ws.addEventListener("message", message => {
43
+ queue.offer(message.data).zipLeft(log.debug("WS", "offer", message.data))
44
+ .runFork
45
+ })
46
+
47
+ ws.addEventListener("error", cause => {
48
+ resume(Effect.fail(new WebSocketError("error", cause)))
49
+ })
50
+
51
+ ws.addEventListener("close", e => {
52
+ resume(Effect.fail(new WebSocketCloseError(e.code, e.reason)))
53
+ })
54
+ })
55
+
56
+ const waitForOpen = (ws: globalThis.WebSocket, timeout: Duration) =>
57
+ Effect.suspend(() => {
58
+ if (ws.readyState === WebSocket.OPEN) {
59
+ return Effect.unit()
60
+ }
61
+
62
+ return Effect.async<never, never, void>(resume => {
63
+ ws.addEventListener("open", () => resume(Effect.unit()), {
64
+ once: true,
65
+ })
66
+ })
67
+ }).timeoutFail(() => new WebSocketError("open-timeout"), timeout)
68
+
69
+ const send = (
70
+ ws: globalThis.WebSocket,
71
+ take: Effect<never, never, Message>,
72
+ log: Log,
73
+ openTimeout: Duration,
74
+ ) => {
75
+ const loop = take
76
+ .tap(data => log.debug("WS", "send", data))
77
+ .tap((data): Effect<never, WebSocketCloseError, void> => {
78
+ if (data === Reconnect) {
79
+ return Effect.failSync(() => {
80
+ ws.close(1012, "reconnecting")
81
+ return new WebSocketCloseError(1012, "reconnecting")
82
+ })
83
+ }
84
+
85
+ return Effect.sync(() => {
86
+ ws.send(data)
87
+ })
88
+ }).forever
89
+
90
+ return waitForOpen(ws, openTimeout).zipRight(loop)
91
+ }
92
+
93
+ const make = Do($ => {
94
+ const log = $(Log)
95
+
96
+ const connect = (
97
+ url: Ref<string>,
98
+ takeOutbound: Effect<never, never, Message>,
99
+ onReconnect = Effect.unit(),
100
+ openTimeout = Duration.seconds(3),
101
+ ) =>
102
+ Do($ => {
103
+ const queue = $(Queue.unbounded<WebSocket.Data>())
104
+
105
+ const run = socket(url)
106
+ .flatMap(ws =>
107
+ offer(ws, queue, log).zipParLeft(
108
+ send(ws, takeOutbound, log, openTimeout),
109
+ ),
110
+ )
111
+ .tapError(_ => (isReconnect(_) ? onReconnect : Effect.unit()))
112
+ .retryWhile(isReconnect).scoped
113
+
114
+ return { run, take: queue.take() } as const
115
+ })
116
+
117
+ return { connect } as const
118
+ })
119
+
120
+ export interface WS extends Effect.Success<typeof make> {}
121
+ export const WS = Tag<WS>()
122
+ export const LiveWS = make.toLayer(WS)
@@ -0,0 +1,43 @@
1
+ import { LiveSharder, Sharder } from "./DiscordGateway/Sharder.js"
2
+
3
+ const fromDispatchFactory =
4
+ <R, E>(source: Stream<R, E, Discord.GatewayPayload<Discord.ReceiveEvent>>) =>
5
+ <K extends keyof Discord.ReceiveEvents>(
6
+ event: K,
7
+ ): Stream<R, E, Discord.ReceiveEvents[K]> =>
8
+ source.filter(p => p.t === event).map(p => p.d! as any)
9
+
10
+ const handleDispatchFactory =
11
+ (hub: Hub<Discord.GatewayPayload<Discord.ReceiveEvent>>) =>
12
+ <K extends keyof Discord.ReceiveEvents, R, E, A>(
13
+ event: K,
14
+ handle: (event: Discord.ReceiveEvents[K]) => Effect<R, E, A>,
15
+ ): Effect<R, E, never> =>
16
+ hub.subscribeForEachPar(_ => {
17
+ if (_.t === event) {
18
+ return handle(_.d as any)
19
+ }
20
+ return Effect.unit()
21
+ })
22
+
23
+ export const make = Do($ => {
24
+ const sharder = $(Sharder)
25
+ const hub = $(Hub.unbounded<Discord.GatewayPayload<Discord.ReceiveEvent>>())
26
+
27
+ const dispatch = Stream.fromHub(hub)
28
+ const fromDispatch = fromDispatchFactory(dispatch)
29
+ const handleDispatch = handleDispatchFactory(hub)
30
+
31
+ const run = sharder.run(hub)
32
+
33
+ return {
34
+ run,
35
+ dispatch,
36
+ fromDispatch,
37
+ handleDispatch,
38
+ }
39
+ })
40
+
41
+ export interface DiscordGateway extends Effect.Success<typeof make> {}
42
+ export const DiscordGateway = Tag<DiscordGateway>()
43
+ export const LiveDiscordGateway = LiveSharder >> make.toLayer(DiscordGateway)
@@ -0,0 +1,13 @@
1
+ import * as Http from "@effect-http/client"
2
+ import { DiscordRESTError } from "dfx/DiscordREST"
3
+ import { Effect } from "dfx/_common"
4
+
5
+ export interface ResponseWithData<A> extends Http.response.Response {
6
+ readonly json: Effect<never, Http.ResponseDecodeError, A>
7
+ }
8
+
9
+ export type RestResponse<T> = Effect<
10
+ never,
11
+ DiscordRESTError,
12
+ ResponseWithData<T>
13
+ >
@@ -0,0 +1,33 @@
1
+ const majorResources = ["channels", "guilds", "webhooks"] as const
2
+
3
+ export const routeFromConfig = (path: string, method: string) => {
4
+ // Only keep major ID's
5
+ const routeURL = path
6
+ .split("?")[0]
7
+ .replace(/\/([A-Za-z]+)\/(\d{16,21}|@me)/g, (match, resource) =>
8
+ majorResources.includes(resource) ? match : `/${resource}`,
9
+ )
10
+ // Strip reactions
11
+ .replace(/\/reactions\/(.*)/, "/reactions")
12
+
13
+ return `${method}-${routeURL}`
14
+ }
15
+
16
+ export const numberHeader = (headers: Headers) => (key: string) =>
17
+ Maybe.fromNullable(headers.get(key))
18
+ .map(parseFloat)
19
+ .filter(n => !isNaN(n))
20
+
21
+ export const retryAfter = (headers: Headers) =>
22
+ numberHeader(headers)("x-ratelimit-reset-after")
23
+ .orElse(() => numberHeader(headers)("retry-after"))
24
+ .map(Duration.seconds)
25
+
26
+ export const rateLimitFromHeaders = (headers: Headers) =>
27
+ Maybe.struct({
28
+ bucket: Maybe.fromNullable(headers.get("x-ratelimit-bucket")),
29
+ retryAfter: retryAfter(headers),
30
+ limit: numberHeader(headers)("x-ratelimit-limit"),
31
+ remaining: numberHeader(headers)("x-ratelimit-remaining"),
32
+ })
33
+ export type RateLimitDetails = ReturnType<typeof rateLimitFromHeaders>
@@ -0,0 +1,203 @@
1
+ import * as Http from "@effect-http/client"
2
+ import { millis } from "@effect/data/Duration"
3
+ import { DiscordConfig } from "./DiscordConfig.js"
4
+ import { ResponseWithData, RestResponse } from "./DiscordREST/types.js"
5
+ import {
6
+ rateLimitFromHeaders,
7
+ retryAfter,
8
+ routeFromConfig,
9
+ } from "./DiscordREST/utils.js"
10
+ import { Log } from "./Log.js"
11
+ import {
12
+ BucketDetails,
13
+ LiveRateLimiter,
14
+ RateLimitStore,
15
+ RateLimiter,
16
+ } from "./RateLimit.js"
17
+ import Pkg from "./package.json" assert { type: "json" }
18
+
19
+ export class DiscordRESTError {
20
+ readonly _tag = "DiscordRESTError"
21
+ constructor(readonly error: Http.HttpClientError) {}
22
+ }
23
+
24
+ const make = Do($ => {
25
+ const { token, rest } = $(DiscordConfig)
26
+
27
+ const http = $(Http.HttpRequestExecutor)
28
+ const log = $(Log)
29
+ const store = $(RateLimitStore)
30
+ const { maybeWait } = $(RateLimiter)
31
+
32
+ const globalRateLimit = maybeWait(
33
+ "dfx.rest.global",
34
+ rest.globalRateLimit.window,
35
+ rest.globalRateLimit.limit,
36
+ )
37
+
38
+ // Invalid route handling (40x)
39
+ const badRoutesRef = $(Ref.make(HashSet.empty<string>()))
40
+ const addBadRoute = (route: string) =>
41
+ Effect.allParDiscard([
42
+ log.info("DiscordREST", "addBadRoute", route),
43
+ badRoutesRef.update(s => s.add(route)),
44
+ store.incrementCounter(
45
+ "dfx.rest.invalid",
46
+ Duration.minutes(10).millis,
47
+ 10000,
48
+ ),
49
+ ])
50
+ const isBadRoute = (route: string) => badRoutesRef.get.map(s => s.has(route))
51
+ const removeBadRoute = (route: string) =>
52
+ badRoutesRef.update(s => s.remove(route))
53
+
54
+ const invalidRateLimit = (route: string) =>
55
+ isBadRoute(route).tap(invalid =>
56
+ invalid
57
+ ? maybeWait("dfx.rest.invalid", Duration.minutes(10), 10000)
58
+ : Effect.unit(),
59
+ ).asUnit
60
+
61
+ // Request rate limiting
62
+ const requestRateLimit = (path: string, request: Http.Request) =>
63
+ Do($ => {
64
+ const route = routeFromConfig(path, request.method)
65
+ const maybeBucket = $(store.getBucketForRoute(route))
66
+ const bucket = maybeBucket.getOrElse(
67
+ (): BucketDetails => ({
68
+ key: `?.${route}`,
69
+ resetAfter: 5000,
70
+ limit: 1,
71
+ }),
72
+ )
73
+ const resetAfter = millis(bucket.resetAfter)
74
+
75
+ $(invalidRateLimit(route))
76
+ $(maybeWait(`dfx.rest.${bucket.key}`, resetAfter, bucket.limit))
77
+ })
78
+
79
+ // Update rate limit buckets
80
+ const updateBuckets = (request: Http.Request, response: Http.Response) =>
81
+ Do($ => {
82
+ const route = routeFromConfig(request.url, request.method)
83
+ const { bucket, retryAfter, limit, remaining } = $(
84
+ rateLimitFromHeaders(response.headers),
85
+ )
86
+
87
+ const effectsToRun = [
88
+ removeBadRoute(route),
89
+ store.putBucketRoute(route, bucket),
90
+ ]
91
+
92
+ const hasBucket = $(store.hasBucket(bucket))
93
+ if (!hasBucket || limit - 1 === remaining) {
94
+ effectsToRun.push(
95
+ store.removeCounter(`dfx.rest.?.${route}`),
96
+ store.putBucket({
97
+ key: bucket,
98
+ resetAfter: retryAfter.millis,
99
+ limit: !hasBucket && remaining > 0 ? remaining : limit,
100
+ }),
101
+ )
102
+ }
103
+
104
+ $(Effect.allParDiscard(effectsToRun))
105
+ }).ignore
106
+
107
+ const httpExecutor = http.execute.filterStatusOk
108
+ .contramap(_ =>
109
+ _.updateUrl(_ => `${rest.baseUrl}${_}`).setHeaders({
110
+ Authorization: `Bot ${token.value}`,
111
+ "User-Agent": `DiscordBot (https://github.com/tim-smart/dfx, ${Pkg.version})`,
112
+ }),
113
+ )
114
+ .catchAll(_ => Effect.fail(new DiscordRESTError(_)))
115
+
116
+ const executor = <A = unknown>(
117
+ request: Http.Request,
118
+ ): Effect<never, DiscordRESTError, ResponseWithData<A>> =>
119
+ Do($ => {
120
+ $(requestRateLimit(request.url, request))
121
+ $(globalRateLimit)
122
+
123
+ const response = $(httpExecutor(request))
124
+
125
+ $(updateBuckets(request, response))
126
+
127
+ return response as ResponseWithData<A>
128
+ }).catchTag("DiscordRESTError", e => {
129
+ if (e.error._tag !== "StatusCodeError") {
130
+ return Effect.fail(e)
131
+ }
132
+
133
+ const response = e.error.response
134
+
135
+ switch (e.error.status) {
136
+ case 403:
137
+ return Do($ => {
138
+ $(
139
+ Effect.allParDiscard([
140
+ log.info("DiscordREST", "403", request.url),
141
+ addBadRoute(routeFromConfig(request.url, request.method)),
142
+ updateBuckets(request, response),
143
+ ]),
144
+ )
145
+ return $(Effect.fail(e))
146
+ })
147
+
148
+ case 429:
149
+ return Do($ => {
150
+ $(
151
+ Effect.allParDiscard([
152
+ log.info("DiscordREST", "429", request.url),
153
+ addBadRoute(routeFromConfig(request.url, request.method)),
154
+ updateBuckets(request, response),
155
+ Effect.sleep(
156
+ retryAfter(response.headers).getOrElse(() =>
157
+ Duration.seconds(5),
158
+ ),
159
+ ),
160
+ ]),
161
+ )
162
+ return $(executor<A>(request))
163
+ })
164
+ }
165
+
166
+ return Effect.fail(e)
167
+ })
168
+
169
+ const routes = Discord.createRoutes<Partial<Http.MakeOptions>>(
170
+ <R, P>({
171
+ method,
172
+ url,
173
+ params,
174
+ options = {},
175
+ }: Discord.Route<P, Partial<Http.MakeOptions>>): RestResponse<R> => {
176
+ const hasBody = method !== "GET" && method !== "DELETE"
177
+ let request = Http.make(method as any)(url, options)
178
+
179
+ if (!hasBody) {
180
+ if (params) {
181
+ request = request.appendParams(params as any)
182
+ }
183
+ } else if (
184
+ params &&
185
+ request.body._tag === "Some" &&
186
+ request.body.value._tag === "FormDataBody"
187
+ ) {
188
+ request.body.value.value.append("payload_json", JSON.stringify(params))
189
+ } else if (params) {
190
+ request = request.jsonBody(params)
191
+ }
192
+
193
+ return executor(request)
194
+ },
195
+ )
196
+
197
+ return { executor, ...routes }
198
+ })
199
+
200
+ export interface DiscordREST extends Effect.Success<typeof make> {}
201
+ export const DiscordREST = Tag<DiscordREST>()
202
+ export const LiveDiscordREST =
203
+ LiveRateLimiter >> Layer.effect(DiscordREST, make)
@@ -0,0 +1,68 @@
1
+ export type Flags<T extends number | bigint> = Record<string, T>
2
+
3
+ /**
4
+ * Returns all the flags OR'ed together.
5
+ */
6
+ export function all(flags: Flags<number>): number
7
+ export function all(flags: Flags<bigint>): bigint
8
+ export function all(flags: Flags<any>): any {
9
+ return Object.values(flags).reduce((acc, flag) => acc | flag)
10
+ }
11
+
12
+ /**
13
+ * Returns a function that converts a bitfield to a list of flag names.
14
+ */
15
+ export function toList<T extends Flags<number>>(
16
+ flags: T,
17
+ ): (bitfield: number) => (keyof T)[]
18
+ export function toList<T extends Flags<bigint>>(
19
+ flags: T,
20
+ ): (bitfield: bigint) => (keyof T)[]
21
+ export function toList<T extends Flags<any>>(
22
+ flags: T,
23
+ ): (bitfield: any) => (keyof T)[] {
24
+ const entries = Object.entries(flags)
25
+ return val =>
26
+ entries.reduce(
27
+ (acc, [key, flag]) => ((val & flag) === flag ? [...acc, key] : acc),
28
+ [] as (keyof T)[],
29
+ )
30
+ }
31
+
32
+ /**
33
+ * Returns a function that converts a list of flags names to a bigint bitfield.
34
+ */
35
+ export const fromListBigint =
36
+ <T extends Flags<bigint>>(flags: T) =>
37
+ (list: (keyof T)[]) =>
38
+ list.reduce((acc, key) => acc | flags[key], BigInt(0))
39
+
40
+ /**
41
+ * Returns a function that converts a list of flags names to a bitfield.
42
+ */
43
+ export const fromList =
44
+ <T extends Flags<number>>(flags: T) =>
45
+ (list: (keyof T)[]) =>
46
+ list.reduce((acc, key) => acc | flags[key], 0)
47
+
48
+ /**
49
+ * Checks if a bigint bitfield contains and a flag value.
50
+ */
51
+ export const hasBigInt = (flag: bigint | string) => {
52
+ const flagBigInt = BigInt(flag)
53
+ return (bits: bigint | string) => {
54
+ const bitsBigInt = BigInt(bits)
55
+ return (bitsBigInt & flagBigInt) === flagBigInt
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Checks if a bitfield contains and a flag value.
61
+ */
62
+ export const has = (flag: number | string) => {
63
+ const flagNumber = +flag
64
+ return (bits: number | string) => {
65
+ const bitsNumber = +bits
66
+ return (bitsNumber & flagNumber) === flagNumber
67
+ }
68
+ }
@@ -0,0 +1,34 @@
1
+ import * as Flags from "dfx/Helpers/flags"
2
+
3
+ /**
4
+ * All the intents
5
+ */
6
+ export const ALL = Flags.all(Discord.GatewayIntents)
7
+
8
+ /**
9
+ * Privileged intents
10
+ */
11
+ export const PRIVILEGED =
12
+ Discord.GatewayIntents.GUILD_PRESENCES |
13
+ Discord.GatewayIntents.GUILD_MEMBERS |
14
+ Discord.GatewayIntents.MESSAGE_CONTENT
15
+
16
+ /**
17
+ * Un-privileged intents
18
+ */
19
+ export const UNPRIVILEGED = ALL ^ PRIVILEGED
20
+
21
+ /**
22
+ * Function that converts a intents bitfield value to a list of intent names.
23
+ */
24
+ export const toList = Flags.toList(Discord.GatewayIntents)
25
+
26
+ /**
27
+ * Function that converts a list of intent names to a bitfield value.
28
+ */
29
+ export const fromList = Flags.fromList(Discord.GatewayIntents)
30
+
31
+ /**
32
+ * Check if an intent flag exists in the permissions.
33
+ */
34
+ export const has = Flags.has