aurival 0.5.0 → 0.7.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 +173 -0
- package/dist/bot.d.ts +51 -0
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +153 -5
- package/dist/bot.js.map +1 -1
- package/dist/caps.d.ts +39 -0
- package/dist/caps.d.ts.map +1 -1
- package/dist/caps.js +41 -0
- package/dist/caps.js.map +1 -1
- package/dist/cooldown.d.ts +164 -0
- package/dist/cooldown.d.ts.map +1 -0
- package/dist/cooldown.js +221 -0
- package/dist/cooldown.js.map +1 -0
- package/dist/embeds.d.ts +38 -0
- package/dist/embeds.d.ts.map +1 -1
- package/dist/embeds.js +59 -5
- package/dist/embeds.js.map +1 -1
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +79 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +142 -17
- package/dist/events.js.map +1 -1
- package/dist/http.d.ts +61 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +76 -4
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/caps.js
CHANGED
|
@@ -56,4 +56,45 @@ export const CAP_BUTTON_MISSING_LABEL = 'Every button needs `label`. A button wi
|
|
|
56
56
|
* refused here rather than sent for the server to refuse as empty text.
|
|
57
57
|
*/
|
|
58
58
|
export const EMPTY_MESSAGE = 'a message needs text, embeds or buttons';
|
|
59
|
+
/**
|
|
60
|
+
* AMENDMENT-07 §7's one new code, `nothing_to_edit`: a PATCH body carrying
|
|
61
|
+
* none of `text`, `embeds` or `buttons` has nothing to change, so the SDK
|
|
62
|
+
* refuses it here rather than spending a round trip on it.
|
|
63
|
+
*
|
|
64
|
+
* The sentence is copied VERBATIM from AMENDMENT-07 §7's table because L1 has
|
|
65
|
+
* not landed `errors_v1.go`'s row yet — when it does, this string and the Go
|
|
66
|
+
* catalogue's must stay byte-identical, as they are for every other sentence
|
|
67
|
+
* shared between `errors_v1.go`, `ERRORS-V1.md` §3, `sdk/python/aurival/caps.py`
|
|
68
|
+
* and this file.
|
|
69
|
+
*/
|
|
70
|
+
export const NOTHING_TO_EDIT = 'an edit needs text, embeds or buttons';
|
|
71
|
+
/**
|
|
72
|
+
* AMENDMENT-08 §4: the command-cooldown notice, sent through `ctx.reply`
|
|
73
|
+
* once per bucket per window. `{name}` is the command as the developer
|
|
74
|
+
* registered it; `{n}` is `Math.max(1, Math.ceil(retryAfterSeconds))`
|
|
75
|
+
* (`cooldown.ts`'s `roundSeconds`) — the SAME rounding rule the button
|
|
76
|
+
* toast's `{n}` uses (§5.4), stated once there. Lives byte-identically in
|
|
77
|
+
* `sdk/python/aurival/caps.py` and the docs page; never a Go template, since
|
|
78
|
+
* the server has no part in a command cooldown.
|
|
79
|
+
*/
|
|
80
|
+
export const COMMAND_COOLDOWN_NOTICE_TEMPLATE = 'Slow down. Try /{name} again in {n} s.';
|
|
81
|
+
/** Renders {@link COMMAND_COOLDOWN_NOTICE_TEMPLATE} for one refusal. */
|
|
82
|
+
export function commandCooldownNotice(name, n) {
|
|
83
|
+
return COMMAND_COOLDOWN_NOTICE_TEMPLATE.replace('{name}', name).replace('{n}', String(n));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* AMENDMENT-08 §5.1/§8's two new ack refusals: `cooldown` mixed with a
|
|
87
|
+
* `text`/`embeds`/`buttons` body, and a `retry_after_ms` outside
|
|
88
|
+
* `[MIN_COOLDOWN_RETRY_AFTER_MS, MAX_COOLDOWN_RETRY_AFTER_MS]`. These DO
|
|
89
|
+
* have a Go twin (`errors_v1.go`'s `CodeCooldownWithBody` /
|
|
90
|
+
* `CodeCooldownRetryAfterInvalid`, §8's table) — unlike `cooldown.ts`'s four
|
|
91
|
+
* attachment-time sentences, which never reach the wire and so are never
|
|
92
|
+
* Go's to send back. L1 has not landed `errors_v1.go`'s rows yet (verified
|
|
93
|
+
* empty on `origin/main`); these ship ahead on §8's authority, following the
|
|
94
|
+
* `NOTHING_TO_EDIT` precedent above.
|
|
95
|
+
*/
|
|
96
|
+
export const MIN_COOLDOWN_RETRY_AFTER_MS = 1;
|
|
97
|
+
export const MAX_COOLDOWN_RETRY_AFTER_MS = 60000;
|
|
98
|
+
export const CAP_COOLDOWN_WITH_BODY = 'a cooldown ack carries no text, embeds or buttons';
|
|
99
|
+
export const CAP_COOLDOWN_RETRY_AFTER_INVALID = `a cooldown retry_after_ms is a whole number of milliseconds between ${MIN_COOLDOWN_RETRY_AFTER_MS} and ${MAX_COOLDOWN_RETRY_AFTER_MS}`;
|
|
59
100
|
//# sourceMappingURL=caps.js.map
|
package/dist/caps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"caps.js","sourceRoot":"","sources":["../src/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AACvC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAIjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,oCAAoC,CAAC;AACxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,0CAA0C,CAAC;AAC9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAC1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,yCAAyC,CAAC;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,sCAAsC,CAAC;AAC7E,MAAM,CAAC,MAAM,uBAAuB,GAAG,6CAA6C,CAAC;AACrF,MAAM,CAAC,MAAM,oBAAoB,GAC/B,gEAAgE,CAAC;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAG,0CAA0C,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,iDAAiD,CAAC;AAC1F,MAAM,CAAC,MAAM,uBAAuB,GAAG,uCAAuC,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,qCAAqC,CAAC;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,kBAAkB,aAAa,CAAC;AAC9F,MAAM,CAAC,MAAM,2BAA2B,GAAG,2BAA2B,CAAC;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAG,kCAAkC,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,0CAA0C,CAAC;AACnF,MAAM,CAAC,MAAM,2BAA2B,GAAG,yCAAyC,CAAC;AACrF,MAAM,CAAC,MAAM,2BAA2B,GAAG,iDAAiD,CAAC;AAC7F,MAAM,CAAC,MAAM,8BAA8B,GAAG,4BAA4B,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,iFAAiF,CAAC;AAEpF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,yCAAyC,CAAC"}
|
|
1
|
+
{"version":3,"file":"caps.js","sourceRoot":"","sources":["../src/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AACvC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAIjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,oCAAoC,CAAC;AACxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,0CAA0C,CAAC;AAC9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAC1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,yCAAyC,CAAC;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,sCAAsC,CAAC;AAC7E,MAAM,CAAC,MAAM,uBAAuB,GAAG,6CAA6C,CAAC;AACrF,MAAM,CAAC,MAAM,oBAAoB,GAC/B,gEAAgE,CAAC;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAG,0CAA0C,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,iDAAiD,CAAC;AAC1F,MAAM,CAAC,MAAM,uBAAuB,GAAG,uCAAuC,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,qCAAqC,CAAC;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,kBAAkB,aAAa,CAAC;AAC9F,MAAM,CAAC,MAAM,2BAA2B,GAAG,2BAA2B,CAAC;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAG,kCAAkC,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,0CAA0C,CAAC;AACnF,MAAM,CAAC,MAAM,2BAA2B,GAAG,yCAAyC,CAAC;AACrF,MAAM,CAAC,MAAM,2BAA2B,GAAG,iDAAiD,CAAC;AAC7F,MAAM,CAAC,MAAM,8BAA8B,GAAG,4BAA4B,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,iFAAiF,CAAC;AAEpF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,yCAAyC,CAAC;AAEvE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,uCAAuC,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,wCAAwC,CAAC;AAEzF,wEAAwE;AACxE,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,CAAS;IAC3D,OAAO,gCAAgC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAC7C,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEjD,MAAM,CAAC,MAAM,sBAAsB,GAAG,mDAAmD,CAAC;AAC1F,MAAM,CAAC,MAAM,gCAAgC,GAAG,uEAAuE,2BAA2B,QAAQ,2BAA2B,EAAE,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Cooldown` — the SDK-side rate-limit primitive (AMENDMENT-08 §2).
|
|
3
|
+
*
|
|
4
|
+
* SDK-side state only: the wire never carries a cooldown configuration, and
|
|
5
|
+
* the server never learns one exists (the one exception is the *button*
|
|
6
|
+
* cooldown ACK, whose `retry_after_ms` is computed FROM this primitive by
|
|
7
|
+
* `bot.ts` — `http.ts::ackCooldown` — never the other way around).
|
|
8
|
+
*
|
|
9
|
+
* **Buckets are process memory. They reset on restart, and they are never
|
|
10
|
+
* shared across instances or processes.** A bot running two workers behind
|
|
11
|
+
* a supervisor has two independent buckets, and a deploy clears every
|
|
12
|
+
* bucket it had. This is stated here, not buried, because a developer who
|
|
13
|
+
* assumes otherwise builds a quota on it — the server floor
|
|
14
|
+
* (`press.go`'s `ButtonPressRateLimit`) is what actually bounds abuse.
|
|
15
|
+
*
|
|
16
|
+
* This module is a leaf: it imports nothing from `embeds.ts`, `events.ts`,
|
|
17
|
+
* `http.ts` or `bot.ts`, so `Cooldown` itself never depends on the wire
|
|
18
|
+
* shapes that use it.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors `sdk/python/aurival/cooldown.py` — the bucket algorithm, the key
|
|
21
|
+
* scheme, and the four cap sentences below are byte/logic-identical between
|
|
22
|
+
* the two SDKs (AMENDMENT-08's shared algorithm block). The command notice
|
|
23
|
+
* template and the two ack-refusal sentences that DO have a server twin live
|
|
24
|
+
* in `caps.ts`, not here — this file's header pins it to sentences with no
|
|
25
|
+
* server twin, exactly as `caps.ts`'s header pins it to sentences that do.
|
|
26
|
+
*/
|
|
27
|
+
/** Who a bucket is scoped to. `user` (default) is one bucket per presser/invoker; `chat` is one per conversation; `global` is one bucket for the whole attachment. */
|
|
28
|
+
export type CooldownBucket = 'user' | 'chat' | 'global';
|
|
29
|
+
/** A plain `{ rate, per, bucket? }` literal — accepted everywhere a `Cooldown` is, and normalized into one at attachment (R-2, R-7). */
|
|
30
|
+
export interface CooldownLiteral {
|
|
31
|
+
rate: number;
|
|
32
|
+
/** seconds */
|
|
33
|
+
per: number;
|
|
34
|
+
bucket?: CooldownBucket | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** A `Cooldown` instance, or a literal that normalizes into one. */
|
|
37
|
+
export type CooldownLike = Cooldown | CooldownLiteral;
|
|
38
|
+
/**
|
|
39
|
+
* The tri-state a cooldown option carries at every one of the three
|
|
40
|
+
* attachment points (`Bot(buttonCooldown)`, card-level `send(...,
|
|
41
|
+
* buttonCooldown)`, `Button(cooldown)`), and at a command's own `cooldown`
|
|
42
|
+
* option: `undefined` — inherit from the level above (or "none", for a
|
|
43
|
+
* command); `null` — explicitly disabled; a `CooldownLike` — use it.
|
|
44
|
+
*/
|
|
45
|
+
export type CooldownOption = CooldownLike | null;
|
|
46
|
+
export declare const CAP_BUTTON_COOLDOWN_TOO_LONG = "a button cooldown is at most 60 seconds";
|
|
47
|
+
export declare const CAP_COOLDOWN_RATE_TOO_LOW = "a cooldown rate is at least 1";
|
|
48
|
+
export declare const CAP_COOLDOWN_PERIOD_NOT_POSITIVE = "a cooldown period is greater than zero";
|
|
49
|
+
export declare const CAP_LINK_BUTTON_COOLDOWN = "a link button cannot carry a cooldown";
|
|
50
|
+
/** §5.1: a button cooldown's `per` is bounded so the ack's `retry_after_ms` never exceeds the wire's 60000ms cap. Command cooldowns are unbounded (D13) — they never reach the wire. */
|
|
51
|
+
export declare const MAX_BUTTON_COOLDOWN_PER_SECONDS = 60;
|
|
52
|
+
/**
|
|
53
|
+
* SDK-side rate limit primitive (AMENDMENT-08 §2). One `Cooldown` instance
|
|
54
|
+
* owns its own bucket map; instances are never shared between attachments —
|
|
55
|
+
* a command's `Cooldown` is private to that command, a per-button
|
|
56
|
+
* `Cooldown` private to that button, and so on.
|
|
57
|
+
*
|
|
58
|
+
* Fixed window anchored at first use, per bucket key. Buckets and the
|
|
59
|
+
* once-per-window notice ledger both live for the process lifetime — the
|
|
60
|
+
* shared algorithm block names no eviction, so none is added here; a bot
|
|
61
|
+
* that runs for a very long time against a huge number of distinct keys
|
|
62
|
+
* accumulates entries for as long as it runs (unlike the bounded card
|
|
63
|
+
* lookup table in `bot.ts`, which is namespaced to message ids and capped).
|
|
64
|
+
*/
|
|
65
|
+
export declare class Cooldown {
|
|
66
|
+
#private;
|
|
67
|
+
readonly rate: number;
|
|
68
|
+
/** seconds */
|
|
69
|
+
readonly per: number;
|
|
70
|
+
readonly bucket: CooldownBucket;
|
|
71
|
+
/**
|
|
72
|
+
* `now` is an injectable monotonic clock (seconds), for tests only — it is
|
|
73
|
+
* deliberately not part of the documented public signature (`new
|
|
74
|
+
* Cooldown(rate, per, bucket)`, matching §2's examples byte for byte).
|
|
75
|
+
*/
|
|
76
|
+
constructor(rate: number, per: number, bucket?: CooldownBucket, now?: () => number);
|
|
77
|
+
/** An existing `Cooldown` passes through unchanged; a plain literal is validated and wrapped. */
|
|
78
|
+
static from(value: CooldownLike, now?: () => number): Cooldown;
|
|
79
|
+
/**
|
|
80
|
+
* One synchronous check-and-consume. `null` on a pass (a token was
|
|
81
|
+
* spent); the seconds remaining on a refusal. **A refusal never consumes
|
|
82
|
+
* and never touches the window** — mashing a refused key does not push
|
|
83
|
+
* the window further out.
|
|
84
|
+
*/
|
|
85
|
+
check(key: string): number | null;
|
|
86
|
+
/**
|
|
87
|
+
* The once-per-window gate for the command notice (§4): `true` only the
|
|
88
|
+
* first time it is called for this key's CURRENT window, `false` for
|
|
89
|
+
* every call after that until the window rolls over. Call this only
|
|
90
|
+
* right after a `check()` refusal on the same key — it reads the window
|
|
91
|
+
* `check()` just refused against (a refusal leaves `windowStart`
|
|
92
|
+
* untouched, so this is safe).
|
|
93
|
+
*/
|
|
94
|
+
noticeOnce(key: string): boolean;
|
|
95
|
+
}
|
|
96
|
+
/** §5's rounding rule, shared by the command sentence's `{n}` and the button ack's `retry_after_ms`: a fractional remainder always rounds UP, and never to zero — a toast or reply that says "0 s" tells the presser to retry immediately, and they would be refused again. */
|
|
97
|
+
export declare function roundSeconds(retryAfterSeconds: number): number;
|
|
98
|
+
/** `retryAfterSeconds` -> the wire's `retry_after_ms`, whole milliseconds, never zero. */
|
|
99
|
+
export declare function retryAfterMs(retryAfterSeconds: number): number;
|
|
100
|
+
/** Refused at ATTACHMENT time (§5): a button cooldown's `per` may not exceed the wire's `retry_after_ms` cap. Command cooldowns never call this — they are unbounded (D13). */
|
|
101
|
+
export declare function requireButtonCooldownBounds(cooldown: Cooldown): void;
|
|
102
|
+
/**
|
|
103
|
+
* Normalizes a tri-state `CooldownOption` at one attachment point:
|
|
104
|
+
* `undefined` stays `undefined` (inherit), `null` stays `null` (disabled), a
|
|
105
|
+
* `CooldownLike` is validated and turned into a `Cooldown`. `boundToButton`
|
|
106
|
+
* runs the 60s cap — pass it for every button-family attachment
|
|
107
|
+
* (`Bot(buttonCooldown)`, card-level, per-button) and leave it off for a
|
|
108
|
+
* command's `cooldown`, which §5 leaves unbounded.
|
|
109
|
+
*/
|
|
110
|
+
export declare function normalizeCooldownOption(value: CooldownOption | undefined, options: {
|
|
111
|
+
boundToButton: boolean;
|
|
112
|
+
}): Cooldown | null | undefined;
|
|
113
|
+
/** `bucket` + the invoking/pressing user id and the chat id -> the subject half of a key (§2's three-bucket table). */
|
|
114
|
+
export declare function subjectKey(bucket: CooldownBucket, userId: string, chatId: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* A button press's full bucket key (§3): the bot-level default has
|
|
117
|
+
* `attachmentScope = ()` — one bucket per user (or chat/global) per bot,
|
|
118
|
+
* regardless of which message or button was pressed — while a card-level or
|
|
119
|
+
* per-button `Cooldown` scopes to `(messageId, buttonId)` too, because
|
|
120
|
+
* button ids are unique only WITHIN a message
|
|
121
|
+
* (`duplicate_button_id`, ERRORS-V1 §3): two cards reusing the same button
|
|
122
|
+
* id must not share a bucket.
|
|
123
|
+
*/
|
|
124
|
+
export declare function buttonBucketKey(cooldown: Cooldown, scope: {
|
|
125
|
+
messageId: string;
|
|
126
|
+
buttonId: string;
|
|
127
|
+
} | null, userId: string, chatId: string): string;
|
|
128
|
+
/** One outgoing card's recorded cooldown configuration (§3's "card lookup on press"). `undefined` in either slot means "inherits from the level above" — the bot default for `cardCooldown`, the card's own resolved cooldown for an entry missing from `byButtonId`. */
|
|
129
|
+
export interface CardCooldownRecord {
|
|
130
|
+
cardCooldown: Cooldown | null | undefined;
|
|
131
|
+
byButtonId: ReadonlyMap<string, Cooldown | null>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Records, per outgoing message id, the card-level cooldown and any
|
|
135
|
+
* per-button overrides a `send()`/`reply()`/`ack()`/`edit()` call attached —
|
|
136
|
+
* because a `button.pressed` event carries only ids, never the `Button`
|
|
137
|
+
* objects that were sent, so precedence has to be resolved from somewhere
|
|
138
|
+
* that remembers them. Bounded LRU-ish (insertion order; `record` moves an
|
|
139
|
+
* existing key to the back) so a bot running for a long time does not grow
|
|
140
|
+
* this table without limit.
|
|
141
|
+
*/
|
|
142
|
+
export declare class CardCooldownTable {
|
|
143
|
+
#private;
|
|
144
|
+
/**
|
|
145
|
+
* `undefined` `cardCooldown` and an empty `byButtonId` together mean
|
|
146
|
+
* "nothing overrides anything on this card" — recording that is a no-op
|
|
147
|
+
* (equivalent to no entry at all, since a lookup miss already falls
|
|
148
|
+
* through to the bot default), and an existing entry for this id is
|
|
149
|
+
* cleared rather than left stale (a card edited to drop its per-button
|
|
150
|
+
* overrides must not keep the old ones).
|
|
151
|
+
*/
|
|
152
|
+
record(messageId: string, entry: CardCooldownRecord): void;
|
|
153
|
+
/** `undefined` when the message is absent from the table — restarted process, or sent by another one (§3: "fall through to the bot default"). */
|
|
154
|
+
lookup(messageId: string): CardCooldownRecord | undefined;
|
|
155
|
+
}
|
|
156
|
+
/** What a press resolved to, and whether it is scoped to `(messageId, buttonId)` when building the bucket key. */
|
|
157
|
+
export interface ResolvedButtonCooldown {
|
|
158
|
+
cooldown: Cooldown | null;
|
|
159
|
+
/** `false` only when the bot-level default is what resolved — `attachmentScope = ()` (§3). */
|
|
160
|
+
scoped: boolean;
|
|
161
|
+
}
|
|
162
|
+
/** Precedence: button > card > bot default (§3), read off the recorded card entry (or its absence). */
|
|
163
|
+
export declare function resolveButtonCooldown(botDefault: Cooldown | null, card: CardCooldownRecord | undefined, buttonId: string): ResolvedButtonCooldown;
|
|
164
|
+
//# sourceMappingURL=cooldown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cooldown.d.ts","sourceRoot":"","sources":["../src/cooldown.ts"],"names":[],"mappings":"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"}
|
package/dist/cooldown.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Cooldown` — the SDK-side rate-limit primitive (AMENDMENT-08 §2).
|
|
3
|
+
*
|
|
4
|
+
* SDK-side state only: the wire never carries a cooldown configuration, and
|
|
5
|
+
* the server never learns one exists (the one exception is the *button*
|
|
6
|
+
* cooldown ACK, whose `retry_after_ms` is computed FROM this primitive by
|
|
7
|
+
* `bot.ts` — `http.ts::ackCooldown` — never the other way around).
|
|
8
|
+
*
|
|
9
|
+
* **Buckets are process memory. They reset on restart, and they are never
|
|
10
|
+
* shared across instances or processes.** A bot running two workers behind
|
|
11
|
+
* a supervisor has two independent buckets, and a deploy clears every
|
|
12
|
+
* bucket it had. This is stated here, not buried, because a developer who
|
|
13
|
+
* assumes otherwise builds a quota on it — the server floor
|
|
14
|
+
* (`press.go`'s `ButtonPressRateLimit`) is what actually bounds abuse.
|
|
15
|
+
*
|
|
16
|
+
* This module is a leaf: it imports nothing from `embeds.ts`, `events.ts`,
|
|
17
|
+
* `http.ts` or `bot.ts`, so `Cooldown` itself never depends on the wire
|
|
18
|
+
* shapes that use it.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors `sdk/python/aurival/cooldown.py` — the bucket algorithm, the key
|
|
21
|
+
* scheme, and the four cap sentences below are byte/logic-identical between
|
|
22
|
+
* the two SDKs (AMENDMENT-08's shared algorithm block). The command notice
|
|
23
|
+
* template and the two ack-refusal sentences that DO have a server twin live
|
|
24
|
+
* in `caps.ts`, not here — this file's header pins it to sentences with no
|
|
25
|
+
* server twin, exactly as `caps.ts`'s header pins it to sentences that do.
|
|
26
|
+
*/
|
|
27
|
+
export const CAP_BUTTON_COOLDOWN_TOO_LONG = 'a button cooldown is at most 60 seconds';
|
|
28
|
+
export const CAP_COOLDOWN_RATE_TOO_LOW = 'a cooldown rate is at least 1';
|
|
29
|
+
export const CAP_COOLDOWN_PERIOD_NOT_POSITIVE = 'a cooldown period is greater than zero';
|
|
30
|
+
export const CAP_LINK_BUTTON_COOLDOWN = 'a link button cannot carry a cooldown';
|
|
31
|
+
/** §5.1: a button cooldown's `per` is bounded so the ack's `retry_after_ms` never exceeds the wire's 60000ms cap. Command cooldowns are unbounded (D13) — they never reach the wire. */
|
|
32
|
+
export const MAX_BUTTON_COOLDOWN_PER_SECONDS = 60;
|
|
33
|
+
/** Seconds, monotonic. `performance.now()` never goes backwards and needs no epoch. */
|
|
34
|
+
function defaultClock() {
|
|
35
|
+
return performance.now() / 1000;
|
|
36
|
+
}
|
|
37
|
+
/** One key's scope + subject, joined with a separator no id in this SDK can contain (`msg_…`/`btn ids`/`usr_…`/`chat_…` are all `[a-zA-Z0-9_-]`). */
|
|
38
|
+
const KEY_SEP = ' ';
|
|
39
|
+
/**
|
|
40
|
+
* SDK-side rate limit primitive (AMENDMENT-08 §2). One `Cooldown` instance
|
|
41
|
+
* owns its own bucket map; instances are never shared between attachments —
|
|
42
|
+
* a command's `Cooldown` is private to that command, a per-button
|
|
43
|
+
* `Cooldown` private to that button, and so on.
|
|
44
|
+
*
|
|
45
|
+
* Fixed window anchored at first use, per bucket key. Buckets and the
|
|
46
|
+
* once-per-window notice ledger both live for the process lifetime — the
|
|
47
|
+
* shared algorithm block names no eviction, so none is added here; a bot
|
|
48
|
+
* that runs for a very long time against a huge number of distinct keys
|
|
49
|
+
* accumulates entries for as long as it runs (unlike the bounded card
|
|
50
|
+
* lookup table in `bot.ts`, which is namespaced to message ids and capped).
|
|
51
|
+
*/
|
|
52
|
+
export class Cooldown {
|
|
53
|
+
rate;
|
|
54
|
+
/** seconds */
|
|
55
|
+
per;
|
|
56
|
+
bucket;
|
|
57
|
+
#now;
|
|
58
|
+
#buckets = new Map();
|
|
59
|
+
#notified = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* `now` is an injectable monotonic clock (seconds), for tests only — it is
|
|
62
|
+
* deliberately not part of the documented public signature (`new
|
|
63
|
+
* Cooldown(rate, per, bucket)`, matching §2's examples byte for byte).
|
|
64
|
+
*/
|
|
65
|
+
constructor(rate, per, bucket = 'user', now = defaultClock) {
|
|
66
|
+
if (rate < 1)
|
|
67
|
+
throw new Error(CAP_COOLDOWN_RATE_TOO_LOW);
|
|
68
|
+
if (!(per > 0))
|
|
69
|
+
throw new Error(CAP_COOLDOWN_PERIOD_NOT_POSITIVE);
|
|
70
|
+
this.rate = rate;
|
|
71
|
+
this.per = per;
|
|
72
|
+
this.bucket = bucket;
|
|
73
|
+
this.#now = now;
|
|
74
|
+
}
|
|
75
|
+
/** An existing `Cooldown` passes through unchanged; a plain literal is validated and wrapped. */
|
|
76
|
+
static from(value, now) {
|
|
77
|
+
if (value instanceof Cooldown)
|
|
78
|
+
return value;
|
|
79
|
+
return now === undefined
|
|
80
|
+
? new Cooldown(value.rate, value.per, value.bucket ?? 'user')
|
|
81
|
+
: new Cooldown(value.rate, value.per, value.bucket ?? 'user', now);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One synchronous check-and-consume. `null` on a pass (a token was
|
|
85
|
+
* spent); the seconds remaining on a refusal. **A refusal never consumes
|
|
86
|
+
* and never touches the window** — mashing a refused key does not push
|
|
87
|
+
* the window further out.
|
|
88
|
+
*/
|
|
89
|
+
check(key) {
|
|
90
|
+
const now = this.#now();
|
|
91
|
+
let state = this.#buckets.get(key);
|
|
92
|
+
if (state === undefined || now - state.windowStart >= this.per) {
|
|
93
|
+
state = { windowStart: now, tokens: this.rate };
|
|
94
|
+
this.#buckets.set(key, state);
|
|
95
|
+
}
|
|
96
|
+
if (state.tokens === 0) {
|
|
97
|
+
return this.per - (now - state.windowStart);
|
|
98
|
+
}
|
|
99
|
+
state.tokens -= 1;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The once-per-window gate for the command notice (§4): `true` only the
|
|
104
|
+
* first time it is called for this key's CURRENT window, `false` for
|
|
105
|
+
* every call after that until the window rolls over. Call this only
|
|
106
|
+
* right after a `check()` refusal on the same key — it reads the window
|
|
107
|
+
* `check()` just refused against (a refusal leaves `windowStart`
|
|
108
|
+
* untouched, so this is safe).
|
|
109
|
+
*/
|
|
110
|
+
noticeOnce(key) {
|
|
111
|
+
const windowStart = this.#buckets.get(key)?.windowStart ?? this.#now();
|
|
112
|
+
if (this.#notified.get(key) === windowStart)
|
|
113
|
+
return false;
|
|
114
|
+
this.#notified.set(key, windowStart);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** §5's rounding rule, shared by the command sentence's `{n}` and the button ack's `retry_after_ms`: a fractional remainder always rounds UP, and never to zero — a toast or reply that says "0 s" tells the presser to retry immediately, and they would be refused again. */
|
|
119
|
+
export function roundSeconds(retryAfterSeconds) {
|
|
120
|
+
return Math.max(1, Math.ceil(retryAfterSeconds));
|
|
121
|
+
}
|
|
122
|
+
/** `retryAfterSeconds` -> the wire's `retry_after_ms`, whole milliseconds, never zero. */
|
|
123
|
+
export function retryAfterMs(retryAfterSeconds) {
|
|
124
|
+
return Math.max(1, Math.ceil(retryAfterSeconds * 1000));
|
|
125
|
+
}
|
|
126
|
+
/** Refused at ATTACHMENT time (§5): a button cooldown's `per` may not exceed the wire's `retry_after_ms` cap. Command cooldowns never call this — they are unbounded (D13). */
|
|
127
|
+
export function requireButtonCooldownBounds(cooldown) {
|
|
128
|
+
if (cooldown.per > MAX_BUTTON_COOLDOWN_PER_SECONDS)
|
|
129
|
+
throw new Error(CAP_BUTTON_COOLDOWN_TOO_LONG);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Normalizes a tri-state `CooldownOption` at one attachment point:
|
|
133
|
+
* `undefined` stays `undefined` (inherit), `null` stays `null` (disabled), a
|
|
134
|
+
* `CooldownLike` is validated and turned into a `Cooldown`. `boundToButton`
|
|
135
|
+
* runs the 60s cap — pass it for every button-family attachment
|
|
136
|
+
* (`Bot(buttonCooldown)`, card-level, per-button) and leave it off for a
|
|
137
|
+
* command's `cooldown`, which §5 leaves unbounded.
|
|
138
|
+
*/
|
|
139
|
+
export function normalizeCooldownOption(value, options) {
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return undefined;
|
|
142
|
+
if (value === null)
|
|
143
|
+
return null;
|
|
144
|
+
const cooldown = Cooldown.from(value);
|
|
145
|
+
if (options.boundToButton)
|
|
146
|
+
requireButtonCooldownBounds(cooldown);
|
|
147
|
+
return cooldown;
|
|
148
|
+
}
|
|
149
|
+
/** `bucket` + the invoking/pressing user id and the chat id -> the subject half of a key (§2's three-bucket table). */
|
|
150
|
+
export function subjectKey(bucket, userId, chatId) {
|
|
151
|
+
if (bucket === 'user')
|
|
152
|
+
return `user${KEY_SEP}${userId}`;
|
|
153
|
+
if (bucket === 'chat')
|
|
154
|
+
return `chat${KEY_SEP}${chatId}`;
|
|
155
|
+
return 'global';
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* A button press's full bucket key (§3): the bot-level default has
|
|
159
|
+
* `attachmentScope = ()` — one bucket per user (or chat/global) per bot,
|
|
160
|
+
* regardless of which message or button was pressed — while a card-level or
|
|
161
|
+
* per-button `Cooldown` scopes to `(messageId, buttonId)` too, because
|
|
162
|
+
* button ids are unique only WITHIN a message
|
|
163
|
+
* (`duplicate_button_id`, ERRORS-V1 §3): two cards reusing the same button
|
|
164
|
+
* id must not share a bucket.
|
|
165
|
+
*/
|
|
166
|
+
export function buttonBucketKey(cooldown, scope, userId, chatId) {
|
|
167
|
+
const subject = subjectKey(cooldown.bucket, userId, chatId);
|
|
168
|
+
return scope === null
|
|
169
|
+
? subject
|
|
170
|
+
: `${scope.messageId}${KEY_SEP}${scope.buttonId}${KEY_SEP}${subject}`;
|
|
171
|
+
}
|
|
172
|
+
/** §3: "bound the table (LRU cap ~1024 messages) so a long-lived bot does not grow without limit." */
|
|
173
|
+
const CARD_TABLE_CAP = 1024;
|
|
174
|
+
/**
|
|
175
|
+
* Records, per outgoing message id, the card-level cooldown and any
|
|
176
|
+
* per-button overrides a `send()`/`reply()`/`ack()`/`edit()` call attached —
|
|
177
|
+
* because a `button.pressed` event carries only ids, never the `Button`
|
|
178
|
+
* objects that were sent, so precedence has to be resolved from somewhere
|
|
179
|
+
* that remembers them. Bounded LRU-ish (insertion order; `record` moves an
|
|
180
|
+
* existing key to the back) so a bot running for a long time does not grow
|
|
181
|
+
* this table without limit.
|
|
182
|
+
*/
|
|
183
|
+
export class CardCooldownTable {
|
|
184
|
+
#entries = new Map();
|
|
185
|
+
/**
|
|
186
|
+
* `undefined` `cardCooldown` and an empty `byButtonId` together mean
|
|
187
|
+
* "nothing overrides anything on this card" — recording that is a no-op
|
|
188
|
+
* (equivalent to no entry at all, since a lookup miss already falls
|
|
189
|
+
* through to the bot default), and an existing entry for this id is
|
|
190
|
+
* cleared rather than left stale (a card edited to drop its per-button
|
|
191
|
+
* overrides must not keep the old ones).
|
|
192
|
+
*/
|
|
193
|
+
record(messageId, entry) {
|
|
194
|
+
if (messageId === '')
|
|
195
|
+
return;
|
|
196
|
+
this.#entries.delete(messageId);
|
|
197
|
+
if (entry.cardCooldown === undefined && entry.byButtonId.size === 0)
|
|
198
|
+
return;
|
|
199
|
+
this.#entries.set(messageId, entry);
|
|
200
|
+
while (this.#entries.size > CARD_TABLE_CAP) {
|
|
201
|
+
const oldest = this.#entries.keys().next().value;
|
|
202
|
+
if (oldest === undefined)
|
|
203
|
+
break;
|
|
204
|
+
this.#entries.delete(oldest);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** `undefined` when the message is absent from the table — restarted process, or sent by another one (§3: "fall through to the bot default"). */
|
|
208
|
+
lookup(messageId) {
|
|
209
|
+
return this.#entries.get(messageId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Precedence: button > card > bot default (§3), read off the recorded card entry (or its absence). */
|
|
213
|
+
export function resolveButtonCooldown(botDefault, card, buttonId) {
|
|
214
|
+
const buttonOverride = card?.byButtonId.get(buttonId);
|
|
215
|
+
if (buttonOverride !== undefined)
|
|
216
|
+
return { cooldown: buttonOverride, scoped: true };
|
|
217
|
+
if (card?.cardCooldown !== undefined)
|
|
218
|
+
return { cooldown: card.cardCooldown, scoped: true };
|
|
219
|
+
return { cooldown: botDefault, scoped: false };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=cooldown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cooldown.js","sourceRoot":"","sources":["../src/cooldown.ts"],"names":[],"mappings":"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"}
|
package/dist/embeds.d.ts
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* throws — the caps are an outbound (client -> server) contract, not an
|
|
18
18
|
* inbound one, and a message already stored by the server is assumed valid.
|
|
19
19
|
*/
|
|
20
|
+
import { Cooldown } from './cooldown.js';
|
|
21
|
+
import type { CooldownOption } from './cooldown.js';
|
|
20
22
|
export interface EmbedFieldValue {
|
|
21
23
|
name: string;
|
|
22
24
|
value: string;
|
|
@@ -84,6 +86,16 @@ export interface ButtonInit {
|
|
|
84
86
|
style?: string;
|
|
85
87
|
emoji?: string;
|
|
86
88
|
url?: string;
|
|
89
|
+
/**
|
|
90
|
+
* AMENDMENT-08 §3: this button's own cooldown, overriding the card-level
|
|
91
|
+
* and bot-default one for this button only (precedence button > card >
|
|
92
|
+
* bot). `undefined` (the default) inherits; `null` disables the default
|
|
93
|
+
* for this one button; a `Cooldown` or a plain `{ rate, per, bucket? }`
|
|
94
|
+
* literal attaches one. Refused at construction — before this button ever
|
|
95
|
+
* reaches the wire — if `per` exceeds 60s, or if this is a `link` button
|
|
96
|
+
* (a link press never round-trips, so it can never carry a cooldown).
|
|
97
|
+
*/
|
|
98
|
+
cooldown?: CooldownOption;
|
|
87
99
|
}
|
|
88
100
|
/** `new Button({ label: 'Pacific' })` — `id` defaults to a slug of `label`, `style` defaults to `'primary'`. */
|
|
89
101
|
export declare class Button {
|
|
@@ -92,6 +104,15 @@ export declare class Button {
|
|
|
92
104
|
style: string;
|
|
93
105
|
emoji?: string;
|
|
94
106
|
url?: string;
|
|
107
|
+
/**
|
|
108
|
+
* This button's own cooldown (AMENDMENT-08 §3), normalized to a real
|
|
109
|
+
* `Cooldown` by `validateButtonShape` (a plain literal in, a `Cooldown`
|
|
110
|
+
* out — `null` passes through unchanged). Absent entirely when the
|
|
111
|
+
* developer never set one, which reads as "inherit" at resolution time.
|
|
112
|
+
* Never serialized: `toJSON()` does not carry it, because a cooldown is
|
|
113
|
+
* SDK-side state that never reaches the wire (§2).
|
|
114
|
+
*/
|
|
115
|
+
cooldown?: Cooldown | null;
|
|
95
116
|
constructor(init: ButtonInit);
|
|
96
117
|
/**
|
|
97
118
|
* The sanctioned way to build a link pill (AMENDMENT-06 §11): a url is not
|
|
@@ -130,6 +151,23 @@ export type ButtonLike = Button | Record<string, unknown>;
|
|
|
130
151
|
* empty, so the caller omits the key.
|
|
131
152
|
*/
|
|
132
153
|
export declare function serialiseEmbeds(embeds: ReadonlyArray<EmbedLike> | undefined): Record<string, unknown>[];
|
|
154
|
+
/** `serialiseButtonsWithCooldowns`'s return shape: the wire JSON, plus every button's cooldown (only the buttons that carried one — inherited buttons are simply absent from the map). */
|
|
155
|
+
export interface SerialisedButtons {
|
|
156
|
+
json: Record<string, unknown>[];
|
|
157
|
+
/** button id -> its own `Cooldown` (`null` means explicitly disabled). A button not in this map inherits from the card, then the bot default (§3). */
|
|
158
|
+
cooldowns: ReadonlyMap<string, Cooldown | null>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Validates and serialises the `buttons` a caller passed to
|
|
162
|
+
* `send`/`reply`/`edit`/`ack`, exactly as `serialiseButtons` does, and ALSO
|
|
163
|
+
* returns each button's own resolved cooldown (AMENDMENT-08 §3) — the
|
|
164
|
+
* per-button overrides `bot.ts`'s card lookup table (`cooldown.ts`'s
|
|
165
|
+
* `CardCooldownTable`) needs to resolve a later press, since the
|
|
166
|
+
* `button.pressed` event that press produces carries only ids, never the
|
|
167
|
+
* `Button` objects this call was given. One validation pass; `serialiseButtons`
|
|
168
|
+
* is a thin wrapper around this for callers that only want the wire shape.
|
|
169
|
+
*/
|
|
170
|
+
export declare function serialiseButtonsWithCooldowns(buttons: ReadonlyArray<ButtonLike> | undefined): SerialisedButtons;
|
|
133
171
|
/**
|
|
134
172
|
* Validates and serialises the `buttons` a caller passed to `send`/`reply`.
|
|
135
173
|
* `[]` when `buttons` is absent or empty, so the caller omits the key.
|
package/dist/embeds.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeds.d.ts","sourceRoot":"","sources":["../src/embeds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"embeds.d.ts","sourceRoot":"","sources":["../src/embeds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiCH,OAAO,EAA4B,QAAQ,EAA2B,MAAM,eAAe,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAwJpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,qBAAa,KAAK;;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,eAAe,EAAE,CAAM;IAC/B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,YAAY,IAAI,GAAE,SAAc,EAmB/B;IAED,0HAA0H;IAC1H,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,IAAI,CAI1D;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAWzD;IAED,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI9B;IAED,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI1B;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAO3C;IAED,oFAAoF;IACpF,YAAY,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAGvC;IAED,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAahC;IAED;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,CA0EpC;CACF;AAkCD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AA+CD,gHAAgH;AAChH,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE3B,YAAY,IAAI,EAAE,UAAU,EAY3B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAKrF;IAED,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKhC;IAED,oEAAoE;IACpE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAuBrC;CACF;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wGAAwG;AACxG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAQpE;AAED,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,CAGtD;AAED,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGxD;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACxD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAU3B;AAED,0LAA0L;AAC1L,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,sJAAsJ;IACtJ,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAC;CACjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,SAAS,GAC7C,iBAAiB,CAenB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,SAAS,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAE3B"}
|