hood-alerts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +369 -0
  3. package/dist/bot/index.cjs +1008 -0
  4. package/dist/bot/index.cjs.map +1 -0
  5. package/dist/bot/index.d.cts +241 -0
  6. package/dist/bot/index.d.ts +241 -0
  7. package/dist/bot/index.js +30 -0
  8. package/dist/bot/index.js.map +1 -0
  9. package/dist/chunk-32GS6XVE.js +732 -0
  10. package/dist/chunk-32GS6XVE.js.map +1 -0
  11. package/dist/chunk-3LHCQA4Z.js +76 -0
  12. package/dist/chunk-3LHCQA4Z.js.map +1 -0
  13. package/dist/chunk-3NHLSKZE.js +1 -0
  14. package/dist/chunk-3NHLSKZE.js.map +1 -0
  15. package/dist/chunk-6JQK5G3Z.js +72 -0
  16. package/dist/chunk-6JQK5G3Z.js.map +1 -0
  17. package/dist/chunk-CBNZ6AZT.js +598 -0
  18. package/dist/chunk-CBNZ6AZT.js.map +1 -0
  19. package/dist/chunk-CIURPPOW.js +1 -0
  20. package/dist/chunk-CIURPPOW.js.map +1 -0
  21. package/dist/chunk-IUOEGBF6.js +49 -0
  22. package/dist/chunk-IUOEGBF6.js.map +1 -0
  23. package/dist/chunk-NBMINJ5E.js +230 -0
  24. package/dist/chunk-NBMINJ5E.js.map +1 -0
  25. package/dist/chunk-O2RY2SWN.js +1 -0
  26. package/dist/chunk-O2RY2SWN.js.map +1 -0
  27. package/dist/chunk-PLMVKTQM.js +1 -0
  28. package/dist/chunk-PLMVKTQM.js.map +1 -0
  29. package/dist/chunk-RSTLW7XH.js +130 -0
  30. package/dist/chunk-RSTLW7XH.js.map +1 -0
  31. package/dist/chunk-SOBZ2FPP.js +355 -0
  32. package/dist/chunk-SOBZ2FPP.js.map +1 -0
  33. package/dist/chunk-VC5MKEY2.js +170 -0
  34. package/dist/chunk-VC5MKEY2.js.map +1 -0
  35. package/dist/chunk-W2GUAWUR.js +147 -0
  36. package/dist/chunk-W2GUAWUR.js.map +1 -0
  37. package/dist/chunk-Y2M3USWZ.js +355 -0
  38. package/dist/chunk-Y2M3USWZ.js.map +1 -0
  39. package/dist/chunk-Z4DJPESQ.js +1216 -0
  40. package/dist/chunk-Z4DJPESQ.js.map +1 -0
  41. package/dist/chunk-ZDM5VNWB.js +1 -0
  42. package/dist/chunk-ZDM5VNWB.js.map +1 -0
  43. package/dist/events/index.cjs +820 -0
  44. package/dist/events/index.cjs.map +1 -0
  45. package/dist/events/index.d.cts +58 -0
  46. package/dist/events/index.d.ts +58 -0
  47. package/dist/events/index.js +69 -0
  48. package/dist/events/index.js.map +1 -0
  49. package/dist/format-6RG4OcyE.d.ts +57 -0
  50. package/dist/format-B7d40f65.d.cts +57 -0
  51. package/dist/index.cjs +4122 -0
  52. package/dist/index.cjs.map +1 -0
  53. package/dist/index.d.cts +21 -0
  54. package/dist/index.d.ts +21 -0
  55. package/dist/index.js +274 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/logger-3phvV_fl.d.cts +185 -0
  58. package/dist/logger-CYza_0eU.d.ts +185 -0
  59. package/dist/logs-D7sYBvSy.d.cts +66 -0
  60. package/dist/logs-D7sYBvSy.d.ts +66 -0
  61. package/dist/notifiers/index.cjs +620 -0
  62. package/dist/notifiers/index.cjs.map +1 -0
  63. package/dist/notifiers/index.d.cts +186 -0
  64. package/dist/notifiers/index.d.ts +186 -0
  65. package/dist/notifiers/index.js +70 -0
  66. package/dist/notifiers/index.js.map +1 -0
  67. package/dist/pricing-BumgLpvB.d.cts +79 -0
  68. package/dist/pricing-BumgLpvB.d.ts +79 -0
  69. package/dist/ratelimit-BKVQG2oY.d.cts +42 -0
  70. package/dist/ratelimit-CGLlXNIS.d.ts +42 -0
  71. package/dist/registry-D3We8Lg6.d.ts +50 -0
  72. package/dist/registry-DePA3IyS.d.cts +50 -0
  73. package/dist/rules/index.cjs +614 -0
  74. package/dist/rules/index.cjs.map +1 -0
  75. package/dist/rules/index.d.cts +166 -0
  76. package/dist/rules/index.d.ts +166 -0
  77. package/dist/rules/index.js +60 -0
  78. package/dist/rules/index.js.map +1 -0
  79. package/dist/schema-DuDMPqpq.d.ts +183 -0
  80. package/dist/schema-NnIdKFl2.d.cts +183 -0
  81. package/dist/service/index.cjs +3774 -0
  82. package/dist/service/index.cjs.map +1 -0
  83. package/dist/service/index.d.cts +262 -0
  84. package/dist/service/index.d.ts +262 -0
  85. package/dist/service/index.js +45 -0
  86. package/dist/service/index.js.map +1 -0
  87. package/dist/service/main.cjs +3759 -0
  88. package/dist/service/main.cjs.map +1 -0
  89. package/dist/service/main.d.cts +1 -0
  90. package/dist/service/main.d.ts +1 -0
  91. package/dist/service/main.js +60 -0
  92. package/dist/service/main.js.map +1 -0
  93. package/dist/sources-CAaQR6-N.d.ts +159 -0
  94. package/dist/sources-CpDOUlzx.d.cts +159 -0
  95. package/dist/telegram-CpodYJrP.d.cts +64 -0
  96. package/dist/telegram-pvphmBwn.d.ts +64 -0
  97. package/dist/tiers/index.cjs +471 -0
  98. package/dist/tiers/index.cjs.map +1 -0
  99. package/dist/tiers/index.d.cts +231 -0
  100. package/dist/tiers/index.d.ts +231 -0
  101. package/dist/tiers/index.js +34 -0
  102. package/dist/tiers/index.js.map +1 -0
  103. package/dist/types-C0ETog04.d.ts +109 -0
  104. package/dist/types-CcoKpStA.d.cts +109 -0
  105. package/dist/types-DFPpTMWo.d.cts +186 -0
  106. package/dist/types-DFPpTMWo.d.ts +186 -0
  107. package/package.json +114 -0
@@ -0,0 +1,241 @@
1
+ import { P as Platform } from '../schema-DuDMPqpq.js';
2
+ import { EntitlementGate } from '../tiers/index.js';
3
+ import { A as AlertStore, c as Logger } from '../logger-CYza_0eU.js';
4
+ import { a as TelegramClientOptions, T as TelegramClient } from '../telegram-pvphmBwn.js';
5
+ import { F as FetchLike } from '../types-C0ETog04.js';
6
+ import 'zod';
7
+ import '../types-DFPpTMWo.js';
8
+ import 'viem';
9
+ import 'hoodchain';
10
+ import '../logs-D7sYBvSy.js';
11
+ import '../registry-D3We8Lg6.js';
12
+ import '../ratelimit-CGLlXNIS.js';
13
+
14
+ /**
15
+ * The bot's command surface, written once and shared by Telegram and Discord.
16
+ *
17
+ * Both platforms deliver the same thing to this router: a command name, a
18
+ * subscriber id, a default delivery target and a list of arguments. Keeping
19
+ * the logic here means the two bots cannot drift apart in what `/rule add`
20
+ * accepts or what `/tier` reports, and it means the command tests exercise the
21
+ * real code path rather than a Telegram-shaped copy of it.
22
+ *
23
+ * Every command answers bad input with a specific, actionable message. There
24
+ * is no silent failure and no raw validation dump: a mistyped threshold gets
25
+ * "usd must be a positive number, for example /threshold whales 5000".
26
+ */
27
+ /** What a platform hands the router. */
28
+ interface CommandContext {
29
+ /** Stable identity for entitlements: `telegram:<user id>`, `discord:<guild or user id>`. */
30
+ subscriberId: string;
31
+ platform: Platform;
32
+ /**
33
+ * Where alerts would go if the user does not name a target: the Telegram
34
+ * chat id, or the Discord channel id the command was used in.
35
+ */
36
+ defaultTarget: string;
37
+ /** Arguments after the command word. */
38
+ args: string[];
39
+ /** The whole argument string, for commands that take free-form JSON. */
40
+ rest: string;
41
+ }
42
+ /** A reply to render back to the user. */
43
+ interface CommandReply {
44
+ /** Telegram HTML, which Discord renders acceptably after tag stripping. */
45
+ html: string;
46
+ /** Plain-text equivalent, used by Discord and by logs. */
47
+ text: string;
48
+ }
49
+ /** A command the bot exposes. */
50
+ interface CommandSpec {
51
+ name: string;
52
+ description: string;
53
+ /** Usage line shown by `/help` and in the README. */
54
+ usage: string;
55
+ /** Discord slash-command options, when the command takes arguments. */
56
+ options?: {
57
+ name: string;
58
+ description: string;
59
+ required: boolean;
60
+ }[];
61
+ }
62
+ /** Everything the router needs. */
63
+ interface CommandRouterDeps {
64
+ store: AlertStore;
65
+ gate: EntitlementGate;
66
+ /** Clock injection point for tests. @defaultValue `Date.now` */
67
+ now?: () => number;
68
+ /** Id factory for new subscriptions and nonces. @defaultValue `randomUUID` */
69
+ newId?: () => string;
70
+ /** Shown by `/upgrade`: how to pay. Set from the service config. */
71
+ upgradeInstructions?: string;
72
+ }
73
+ /** The router. */
74
+ interface CommandRouter {
75
+ /** Every command, for `/help` and for Discord registration. */
76
+ readonly commands: readonly CommandSpec[];
77
+ /** Handle one command. Never throws: errors come back as a reply. */
78
+ handle(command: string, context: CommandContext): Promise<CommandReply>;
79
+ }
80
+ /** The command catalogue. */
81
+ declare const COMMANDS: readonly CommandSpec[];
82
+ /** Build the command router. */
83
+ declare function createCommandRouter(deps: CommandRouterDeps): CommandRouter;
84
+
85
+ /**
86
+ * The Telegram bot: long polling, because it needs no public URL, no TLS
87
+ * certificate and no inbound firewall rule. A self-hoster runs the container
88
+ * and the bot works; a webhook deployment would need all three before the
89
+ * first `/start`.
90
+ *
91
+ * `getUpdates` is called with a long timeout, so the loop is idle rather than
92
+ * busy between messages, and the offset is advanced past every update the loop
93
+ * has handled so a restart never re-processes an old command.
94
+ */
95
+ /** Options for {@link createTelegramBot}. */
96
+ interface TelegramBotOptions extends TelegramClientOptions {
97
+ router: CommandRouter;
98
+ logger?: Logger;
99
+ /** Reuse the notifier's client so both share one rate-limit path. */
100
+ client?: TelegramClient;
101
+ /** Long-poll timeout in seconds. @defaultValue `30` */
102
+ pollTimeoutSeconds?: number;
103
+ /** Persist the update offset so a restart resumes cleanly. */
104
+ offsetStore?: {
105
+ get(): number | null;
106
+ set(offset: number): void;
107
+ };
108
+ }
109
+ /** A running bot. */
110
+ interface TelegramBot {
111
+ /** Poll once and handle whatever arrived. Returns how many updates it handled. */
112
+ pollOnce(): Promise<number>;
113
+ /** Publish the command list so Telegram shows the `/` menu. */
114
+ publishCommands(): Promise<boolean>;
115
+ /** Start the loop. Resolves when {@link TelegramBot.stop} is called. */
116
+ start(): Promise<void>;
117
+ /** Stop the loop after the current poll. */
118
+ stop(): void;
119
+ }
120
+ /**
121
+ * Build the Telegram bot.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const bot = createTelegramBot({ botToken: process.env.TELEGRAM_BOT_TOKEN!, router })
126
+ * await bot.publishCommands()
127
+ * await bot.start()
128
+ * ```
129
+ */
130
+ declare function createTelegramBot(options: TelegramBotOptions): TelegramBot;
131
+
132
+ /**
133
+ * The Discord bot: slash commands over the HTTP interactions endpoint.
134
+ *
135
+ * Discord offers two ways to receive commands: a persistent gateway
136
+ * WebSocket, or an HTTPS interactions endpoint. This package uses the
137
+ * endpoint, because the service already runs an HTTP server for `/health` and
138
+ * `/metrics`, and an endpoint scales to zero and survives a restart with no
139
+ * reconnect logic. The price is that every request must be signature-verified,
140
+ * which Discord enforces: it sends deliberately invalid signatures during
141
+ * setup and refuses to save an endpoint that answers them with anything other
142
+ * than `401`.
143
+ *
144
+ * Verification is Ed25519 over `timestamp + rawBody`, using the application's
145
+ * public key. Node's `crypto` verifies Ed25519 natively, so this needs no
146
+ * dependency: the raw 32-byte key is wrapped in the fixed SPKI DER prefix for
147
+ * `id-Ed25519` (RFC 8410) and handed to `createPublicKey`.
148
+ */
149
+ /** Discord's interaction type numbers, for the two kinds handled here. */
150
+ declare const INTERACTION_TYPE: {
151
+ readonly PING: 1;
152
+ readonly APPLICATION_COMMAND: 2;
153
+ };
154
+ /** Discord's interaction callback types. */
155
+ declare const CALLBACK_TYPE: {
156
+ readonly PONG: 1;
157
+ readonly CHANNEL_MESSAGE_WITH_SOURCE: 4;
158
+ };
159
+ /** Discord marks a message ephemeral (only the caller sees it) with this flag. */
160
+ declare const EPHEMERAL_FLAG: number;
161
+ /**
162
+ * Verify a Discord interaction signature.
163
+ *
164
+ * @param publicKey the application's hex public key from the developer portal.
165
+ * @returns `true` only for a signature that verifies. Any malformed input is
166
+ * `false`, never a throw: a crash here would answer Discord with a 500 and
167
+ * fail its endpoint validation just as surely as a wrong answer.
168
+ */
169
+ declare function verifyDiscordSignature(publicKey: string, signature: string, timestamp: string, rawBody: string): boolean;
170
+ /** A Discord slash-command definition, as the API expects it. */
171
+ interface DiscordCommandDefinition {
172
+ name: string;
173
+ description: string;
174
+ /** Option type 3 is STRING, the only type these commands need. */
175
+ options?: {
176
+ type: 3;
177
+ name: string;
178
+ description: string;
179
+ required: boolean;
180
+ }[];
181
+ }
182
+ /** Convert the shared command catalogue into Discord slash-command definitions. */
183
+ declare function discordCommandDefinitions(commands: readonly CommandSpec[]): DiscordCommandDefinition[];
184
+ /** Options for {@link registerDiscordCommands}. */
185
+ interface RegisterCommandsOptions {
186
+ applicationId: string;
187
+ botToken: string;
188
+ /** Register to one guild (instant) instead of globally (up to an hour to propagate). */
189
+ guildId?: string;
190
+ apiBase?: string;
191
+ fetch?: FetchLike;
192
+ }
193
+ /**
194
+ * Register slash commands with Discord.
195
+ *
196
+ * @returns the number of commands Discord accepted.
197
+ * @throws when Discord rejects the registration, with its own error message
198
+ * attached: a silent failure here means a bot that appears installed and has
199
+ * no commands.
200
+ *
201
+ * @example
202
+ * ```bash
203
+ * DISCORD_APPLICATION_ID=... DISCORD_BOT_TOKEN=... npm run register:discord
204
+ * ```
205
+ */
206
+ declare function registerDiscordCommands(commands: readonly CommandSpec[], options: RegisterCommandsOptions): Promise<number>;
207
+ /** What to answer Discord with. */
208
+ interface InteractionResponse {
209
+ status: number;
210
+ body: unknown;
211
+ }
212
+ /** Options for {@link createDiscordInteractionHandler}. */
213
+ interface DiscordInteractionOptions {
214
+ router: CommandRouter;
215
+ /** The application's public key, for signature verification. */
216
+ publicKey: string;
217
+ /** Reply only to the caller. @defaultValue `true` */
218
+ ephemeral?: boolean;
219
+ }
220
+ /**
221
+ * Build the interactions handler. Mount it on the service's HTTP server at
222
+ * whatever path is configured as the application's Interactions Endpoint URL.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * const interactions = createDiscordInteractionHandler({ router, publicKey })
227
+ * // inside the HTTP server:
228
+ * const answer = await interactions.handle({ signature, timestamp, rawBody })
229
+ * res.writeHead(answer.status, { 'content-type': 'application/json' })
230
+ * res.end(JSON.stringify(answer.body))
231
+ * ```
232
+ */
233
+ declare function createDiscordInteractionHandler(options: DiscordInteractionOptions): {
234
+ handle(request: {
235
+ signature: string | null;
236
+ timestamp: string | null;
237
+ rawBody: string;
238
+ }): Promise<InteractionResponse>;
239
+ };
240
+
241
+ export { CALLBACK_TYPE, COMMANDS, type CommandContext, type CommandReply, type CommandRouter, type CommandRouterDeps, type CommandSpec, type DiscordCommandDefinition, type DiscordInteractionOptions, EPHEMERAL_FLAG, INTERACTION_TYPE, type InteractionResponse, type RegisterCommandsOptions, type TelegramBot, type TelegramBotOptions, createCommandRouter, createDiscordInteractionHandler, createTelegramBot, discordCommandDefinitions, registerDiscordCommands, verifyDiscordSignature };
@@ -0,0 +1,30 @@
1
+ import "../chunk-ZDM5VNWB.js";
2
+ import {
3
+ CALLBACK_TYPE,
4
+ COMMANDS,
5
+ EPHEMERAL_FLAG,
6
+ INTERACTION_TYPE,
7
+ createCommandRouter,
8
+ createDiscordInteractionHandler,
9
+ createTelegramBot,
10
+ discordCommandDefinitions,
11
+ registerDiscordCommands,
12
+ verifyDiscordSignature
13
+ } from "../chunk-32GS6XVE.js";
14
+ import "../chunk-NBMINJ5E.js";
15
+ import "../chunk-W2GUAWUR.js";
16
+ import "../chunk-VC5MKEY2.js";
17
+ import "../chunk-3LHCQA4Z.js";
18
+ export {
19
+ CALLBACK_TYPE,
20
+ COMMANDS,
21
+ EPHEMERAL_FLAG,
22
+ INTERACTION_TYPE,
23
+ createCommandRouter,
24
+ createDiscordInteractionHandler,
25
+ createTelegramBot,
26
+ discordCommandDefinitions,
27
+ registerDiscordCommands,
28
+ verifyDiscordSignature
29
+ };
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}