stitchkit 0.100.3 → 0.101.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/CHANGELOG.md +67 -0
- package/dist/entrypoints/application.js +2 -1
- package/dist/entrypoints/observability.js +2 -1
- package/dist/entrypoints/telegram/screens.js +1 -1
- package/dist/entrypoints/telegram.d.ts +8 -4
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +277 -49
- package/dist/entrypoints/tools.js +2 -1
- package/dist/{index-6k1svmjh.js → index-e05cjgtf.js} +16 -13
- package/dist/{index-kzz6rq0w.js → index-e6c2spsd.js} +56 -18
- package/dist/index-sznqf8fx.js +20 -0
- package/dist/internal/secret-shapes.d.ts +21 -0
- package/dist/internal/secret-shapes.d.ts.map +1 -0
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/telegram/bot-api.d.ts +7 -4
- package/dist/telegram/bot-api.d.ts.map +1 -1
- package/dist/telegram/init-data.d.ts +3 -44
- package/dist/telegram/init-data.d.ts.map +1 -1
- package/dist/telegram/operator-channel.d.ts +1 -18
- package/dist/telegram/operator-channel.d.ts.map +1 -1
- package/dist/telegram/operator-dedupe.d.ts +44 -0
- package/dist/telegram/operator-dedupe.d.ts.map +1 -0
- package/dist/telegram/send-failure.d.ts +6 -0
- package/dist/telegram/send-failure.d.ts.map +1 -1
- package/dist/telegram/update-intake.d.ts +5 -1
- package/dist/telegram/update-intake.d.ts.map +1 -1
- package/dist/telegram/update-store-check.d.ts +22 -0
- package/dist/telegram/update-store-check.d.ts.map +1 -0
- package/dist/telegram/update-store-postgres.d.ts +33 -0
- package/dist/telegram/update-store-postgres.d.ts.map +1 -0
- package/dist/telegram/user.d.ts +51 -0
- package/dist/telegram/user.d.ts.map +1 -0
- package/llms/observability.txt +13 -1
- package/llms/server.4.txt +16 -0
- package/llms/stitchkit.txt +3 -3
- package/llms/telegram.txt +91 -13
- package/llms/{upgrading-0.74.0-0.100.0.txt → upgrading-0.74.0-0.101.0.txt} +39 -3
- package/llms/upgrading.txt +1 -1
- package/llms-full.txt +159 -17
- package/llms.txt +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,73 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [0.101.0] — 2026-09-29
|
|
19
|
+
|
|
20
|
+
### ⚠️ Breaking changes
|
|
21
|
+
|
|
22
|
+
- **`stitchkit/telegram`** — `TelegramInitDataUser` is `TelegramUser`. The same
|
|
23
|
+
camelCase record now also comes out of `parseTelegramUser(ctx.from)`, so its
|
|
24
|
+
name says any Telegram user. `import type { TelegramInitDataUser }` →
|
|
25
|
+
`import type { TelegramUser }`.
|
|
26
|
+
- **`stitchkit/telegram`** — `TELEGRAM_BOT_TOKEN_PATTERN` matches the secret
|
|
27
|
+
half of a token only, after `<bot id>:`, so masking with it keeps the bot's id
|
|
28
|
+
(`123456:[redacted]`) — the only thing in a masked line that says which bot
|
|
29
|
+
failed. Masking no longer needs it at all: `redact` masks bot tokens by shape.
|
|
30
|
+
`sensitiveUrlPatterns: [TELEGRAM_BOT_TOKEN_PATTERN]` → nothing;
|
|
31
|
+
`text.replace(new RegExp(TELEGRAM_BOT_TOKEN_PATTERN.source, 'g'), …)` →
|
|
32
|
+
`redactTelegramBotToken(text)`. See ADR 0214 and the 0.101.0 migration in
|
|
33
|
+
`docs/guide/upgrading.md`.
|
|
34
|
+
|
|
35
|
+
**Who must act:** code that names the type `TelegramInitDataUser` (rename the
|
|
36
|
+
import), and code that uses `TELEGRAM_BOT_TOKEN_PATTERN` to find or cut a whole
|
|
37
|
+
token. Code that only passes the pattern to a logger keeps working — and can
|
|
38
|
+
drop it, since tokens are masked by shape.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `stitchkit/telegram` — `postgresTelegramUpdateStore`: the webhook update store
|
|
43
|
+
in Postgres over one `query(text, parameters)` the application already has —
|
|
44
|
+
Bun `sql.unsafe`, Prisma `$queryRawUnsafe`, `pg` `pool.query` — each rule one
|
|
45
|
+
conditional statement with explicit casts, so a claim is atomic across
|
|
46
|
+
processes and a driver returning `bigint` as a string or a `bigint` reads the
|
|
47
|
+
same. `postgresTelegramUpdateStoreSchema(table)` is the table for a migration;
|
|
48
|
+
`createTable: false` leaves the schema to it.
|
|
49
|
+
- `stitchkit/telegram` — `checkTelegramUpdateStore(make)`: every rule of
|
|
50
|
+
`TelegramUpdateStore` on a fresh store, eight claims raced against each other,
|
|
51
|
+
resolving with the violations. A store of one's own over an ORM is checked by
|
|
52
|
+
the rules the shipped memory, SQLite and Postgres stores pass.
|
|
53
|
+
- `stitchkit/telegram` — `createTelegramOperatorDedupe`: the operator channel's
|
|
54
|
+
dedupe window alone, for a bot that sends alerts itself — `(text, topic?)` is
|
|
55
|
+
`{ send: true, text }` with the held-back counts, or `{ send: false, reason }`.
|
|
56
|
+
- `stitchkit/telegram` — `parseTelegramUser`: grammY's `ctx.from`, a Bot API
|
|
57
|
+
`User` or a Mini App `user` as the one `TelegramUser` initData returns.
|
|
58
|
+
- `stitchkit/telegram` — `TELEGRAM_SEND_FAILURE_REASONS`, every reason as a
|
|
59
|
+
list; the reasons' traits are one table, so a new reason without its traits
|
|
60
|
+
does not compile.
|
|
61
|
+
- `stitchkit/telegram` — `redactTelegramBotToken(text)`.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- `stitchkit/observability` — `redact`, and so every logger and audit sink,
|
|
66
|
+
masks secrets by shape in every string: the secret half of a bot token, the
|
|
67
|
+
password in `scheme://user:password@host`, and the value of a query parameter
|
|
68
|
+
named as a secret (`token`, `access_token`, `api-key`, `key`, `sig`, …). A URL
|
|
69
|
+
in an error message is the usual way a token reaches a journal, and no key
|
|
70
|
+
name marks it.
|
|
71
|
+
- `stitchkit/observability` — an error keeps its own fields: a
|
|
72
|
+
`TelegramBotApiError`'s `error_code` and `parameters`, a provider's `status`,
|
|
73
|
+
written beside `name`, `message`, `stack` and `cause` and masked by the same
|
|
74
|
+
rules. A field holding a class instance other than an error — grammY's
|
|
75
|
+
`BotError.ctx` — is written as its class name, so one error cannot push its
|
|
76
|
+
line past the bound and lose the stack.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- `stitchkit/telegram` — the update intake unwraps grammY's `BotError`: a
|
|
81
|
+
`retry`, an `onFailure` and the error stored with the update saw the wrapper
|
|
82
|
+
("Error in middleware: …") instead of what the middleware threw, so a retry
|
|
83
|
+
policy could not recognise the bot's own terminal error and retried it.
|
|
84
|
+
|
|
18
85
|
## [0.100.3] — 2026-09-29
|
|
19
86
|
|
|
20
87
|
### Added
|
|
@@ -73,9 +73,10 @@ import {
|
|
|
73
73
|
ApplicationAdmissionError2,
|
|
74
74
|
createApplication2
|
|
75
75
|
} from "../index-w575evhq.js";
|
|
76
|
+
import"../index-sznqf8fx.js";
|
|
76
77
|
import {
|
|
77
78
|
measureSize2
|
|
78
|
-
} from "../index-
|
|
79
|
+
} from "../index-e6c2spsd.js";
|
|
79
80
|
import"../index-rfkh44ss.js";
|
|
80
81
|
import {
|
|
81
82
|
diff,
|
|
@@ -19,12 +19,13 @@ import {
|
|
|
19
19
|
childSpan2,
|
|
20
20
|
resolvePropagationContext2
|
|
21
21
|
} from "../index-hby4qhax.js";
|
|
22
|
+
import"../index-sznqf8fx.js";
|
|
22
23
|
import {
|
|
23
24
|
redact2,
|
|
24
25
|
truncatePreview2,
|
|
25
26
|
sanitizePayload2,
|
|
26
27
|
measureSize2
|
|
27
|
-
} from "../index-
|
|
28
|
+
} from "../index-e6c2spsd.js";
|
|
28
29
|
import {
|
|
29
30
|
runWithRequestContext2,
|
|
30
31
|
getRequestContext2,
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export { callTelegramBotApi, TELEGRAM_BOT_TOKEN_PATTERN, type TelegramBotApiCall, TelegramBotApiError, } from '../telegram/bot-api.js';
|
|
1
|
+
export { callTelegramBotApi, redactTelegramBotToken, TELEGRAM_BOT_TOKEN_PATTERN, type TelegramBotApiCall, TelegramBotApiError, } from '../telegram/bot-api.js';
|
|
2
2
|
export { runTelegramBroadcast, type TelegramBroadcastConfig, type TelegramBroadcastMessage, type TelegramBroadcastOutcome, type TelegramBroadcastRecipient, type TelegramBroadcastReport, type TelegramBroadcastRunOutcome, type TelegramBroadcastSend, type TelegramBroadcastSenderConfig, telegramBroadcastSender, } from '../telegram/broadcast.js';
|
|
3
|
-
export { type TelegramInitData, type TelegramInitDataRefusal, type
|
|
3
|
+
export { type TelegramInitData, type TelegramInitDataRefusal, type TelegramInitDataVerification, type VerifyTelegramInitDataOptions, verifyTelegramInitData, } from '../telegram/init-data.js';
|
|
4
4
|
export { createTelegramLocalFiles, TelegramLocalFileError, type TelegramLocalFileRefusal, type TelegramLocalFiles, type TelegramLocalFilesCheck, type TelegramLocalFilesConfig, } from '../telegram/local-files.js';
|
|
5
|
-
export { createTelegramOperatorChannel, type TelegramChatId, type TelegramOperatorChannel, type TelegramOperatorChannelConfig, type
|
|
6
|
-
export {
|
|
5
|
+
export { createTelegramOperatorChannel, type TelegramChatId, type TelegramOperatorChannel, type TelegramOperatorChannelConfig, type TelegramOperatorDrop, type TelegramOperatorMessage, type TelegramOperatorSenderConfig, telegramOperatorSender, } from '../telegram/operator-channel.js';
|
|
6
|
+
export { createTelegramOperatorDedupe, type TelegramOperatorDedupe, type TelegramOperatorDedupeVerdict, } from '../telegram/operator-dedupe.js';
|
|
7
|
+
export { classifyTelegramSendFailure, TELEGRAM_SEND_FAILURE_REASONS, type TelegramSendFailure, type TelegramSendFailureReason, } from '../telegram/send-failure.js';
|
|
7
8
|
export { createTelegramUpdateIntake, type TelegramUpdateEnvelope, type TelegramUpdateFailure, type TelegramUpdateIntake, type TelegramUpdateIntakeConfig, type TelegramUpdateStoreStep, } from '../telegram/update-intake.js';
|
|
8
9
|
export { memoryTelegramUpdateStore, type StoredTelegramUpdate, type TelegramUpdateClaimOptions, type TelegramUpdateDueQuery, type TelegramUpdateSettlement, type TelegramUpdateState, type TelegramUpdateStore, } from '../telegram/update-store.js';
|
|
10
|
+
export { checkTelegramUpdateStore } from '../telegram/update-store-check.js';
|
|
11
|
+
export { type PostgresTelegramUpdateStoreConfig, postgresTelegramUpdateStore, postgresTelegramUpdateStoreSchema, type TelegramPostgresQuery, } from '../telegram/update-store-postgres.js';
|
|
9
12
|
export { type SqliteTelegramUpdateStoreConfig, sqliteTelegramUpdateStore, type TelegramSqliteDatabase, } from '../telegram/update-store-sqlite.js';
|
|
13
|
+
export { parseTelegramUser, type TelegramUser } from '../telegram/user.js';
|
|
10
14
|
export { type ClaimTelegramWebhookConfig, checkTelegramWebhook, claimTelegramWebhook, type ReceiveTelegramWebhookOptions, receiveTelegramWebhook, TELEGRAM_WEBHOOK_NONE, type TelegramUpdateAcceptance, type TelegramWebhookClaim, TelegramWebhookClaimError, type TelegramWebhookConfig, type TelegramWebhookRefusal, type TelegramWebhookState, telegramWebhookUrl, } from '../telegram/webhook.js';
|
|
11
15
|
//# sourceMappingURL=telegram.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/entrypoints/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,
|
|
1
|
+
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/entrypoints/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAClC,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,sBAAsB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,GACnC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yBAAyB,EACzB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EACL,KAAK,iCAAiC,EACtC,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,qBAAqB,GAC3B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,+BAA+B,EACpC,yBAAyB,EACzB,KAAK,sBAAsB,GAC5B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EACL,KAAK,0BAA0B,EAC/B,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,6BAA6B,EAClC,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BOT_TOKEN_SECRET
|
|
3
|
+
} from "../index-sznqf8fx.js";
|
|
4
|
+
import {
|
|
5
|
+
TELEGRAM_SEND_FAILURE_REASONS2,
|
|
2
6
|
classifyTelegramSendFailure2
|
|
3
|
-
} from "../index-
|
|
7
|
+
} from "../index-e05cjgtf.js";
|
|
4
8
|
import {
|
|
5
9
|
TELEGRAM_TEXT_LIMIT2,
|
|
6
10
|
truncateTelegramHtml2
|
|
@@ -11,7 +15,10 @@ import {
|
|
|
11
15
|
} from "../index-9jfdm5tg.js";
|
|
12
16
|
|
|
13
17
|
// src/telegram/bot-api.ts
|
|
14
|
-
var TELEGRAM_BOT_TOKEN_PATTERN =
|
|
18
|
+
var TELEGRAM_BOT_TOKEN_PATTERN = BOT_TOKEN_SECRET;
|
|
19
|
+
function redactTelegramBotToken(text) {
|
|
20
|
+
return text.replace(new RegExp(BOT_TOKEN_SECRET.source, "g"), "[redacted]");
|
|
21
|
+
}
|
|
15
22
|
var DEFAULT_API_ROOT = "https://api.telegram.org";
|
|
16
23
|
|
|
17
24
|
class TelegramBotApiError extends Error {
|
|
@@ -336,9 +343,6 @@ function telegramBroadcastSender(config) {
|
|
|
336
343
|
...config.fetch && { fetch: config.fetch }
|
|
337
344
|
});
|
|
338
345
|
}
|
|
339
|
-
// src/telegram/init-data.ts
|
|
340
|
-
import { z as z2 } from "zod";
|
|
341
|
-
|
|
342
346
|
// src/telegram/crypto.ts
|
|
343
347
|
var encoder = new TextEncoder;
|
|
344
348
|
async function hmacSha256(key, message) {
|
|
@@ -358,9 +362,9 @@ function digestsEqual(a, b) {
|
|
|
358
362
|
return difference === 0;
|
|
359
363
|
}
|
|
360
364
|
|
|
361
|
-
// src/telegram/
|
|
362
|
-
|
|
363
|
-
var
|
|
365
|
+
// src/telegram/user.ts
|
|
366
|
+
import { z as z2 } from "zod";
|
|
367
|
+
var TelegramUserSchema = z2.object({
|
|
364
368
|
id: z2.int(),
|
|
365
369
|
is_bot: z2.boolean().optional(),
|
|
366
370
|
first_name: z2.string(),
|
|
@@ -381,11 +385,21 @@ var TelegramInitDataUserSchema = z2.object({
|
|
|
381
385
|
...user.allows_write_to_pm !== undefined && { allowsWriteToPm: user.allows_write_to_pm },
|
|
382
386
|
...user.photo_url !== undefined && { photoUrl: user.photo_url }
|
|
383
387
|
}));
|
|
388
|
+
function parseTelegramUser(value) {
|
|
389
|
+
const parsed = TelegramUserSchema.safeParse(value);
|
|
390
|
+
return parsed.success ? parsed.data : undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/telegram/init-data.ts
|
|
394
|
+
var encoder2 = new TextEncoder;
|
|
384
395
|
function optionalUser(raw, key) {
|
|
385
396
|
const encoded = raw[key];
|
|
386
397
|
if (encoded === undefined)
|
|
387
398
|
return;
|
|
388
|
-
|
|
399
|
+
const user = parseTelegramUser(JSON.parse(encoded));
|
|
400
|
+
if (user === undefined)
|
|
401
|
+
throw new TypeError(`initData ${key} is not a Telegram user`);
|
|
402
|
+
return user;
|
|
389
403
|
}
|
|
390
404
|
async function verifyTelegramInitData(options) {
|
|
391
405
|
if (!options.botToken)
|
|
@@ -531,42 +545,12 @@ function createTelegramLocalFiles(config) {
|
|
|
531
545
|
}
|
|
532
546
|
};
|
|
533
547
|
}
|
|
534
|
-
// src/telegram/operator-
|
|
535
|
-
var MASK = "[redacted]";
|
|
536
|
-
function masked(text, patterns) {
|
|
537
|
-
let output = text;
|
|
538
|
-
for (const pattern of patterns) {
|
|
539
|
-
output = output.replace(new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`), MASK);
|
|
540
|
-
}
|
|
541
|
-
return output;
|
|
542
|
-
}
|
|
543
|
-
function literal(value) {
|
|
544
|
-
return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
545
|
-
}
|
|
546
|
-
function timer(milliseconds, signal) {
|
|
547
|
-
return new Promise((resolve, reject) => {
|
|
548
|
-
if (signal.aborted)
|
|
549
|
-
return reject(signal.reason);
|
|
550
|
-
const handle = setTimeout(() => {
|
|
551
|
-
signal.removeEventListener("abort", stop);
|
|
552
|
-
resolve();
|
|
553
|
-
}, milliseconds);
|
|
554
|
-
const stop = () => {
|
|
555
|
-
clearTimeout(handle);
|
|
556
|
-
reject(signal.reason);
|
|
557
|
-
};
|
|
558
|
-
signal.addEventListener("abort", stop, { once: true });
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
function fitted(text) {
|
|
562
|
-
const points = Array.from(text);
|
|
563
|
-
return points.length <= TELEGRAM_TEXT_LIMIT2 ? text : `${points.slice(0, TELEGRAM_TEXT_LIMIT2 - 1).join("")}…`;
|
|
564
|
-
}
|
|
548
|
+
// src/telegram/operator-dedupe.ts
|
|
565
549
|
function defaultFingerprint(text, topic) {
|
|
566
550
|
const blanked = text.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "#").replace(/\b[a-z0-9]{20,}\b/gi, "#").replace(/\d+/g, "#");
|
|
567
551
|
return `${topic ?? ""}\x00${blanked.slice(0, 300)}`;
|
|
568
552
|
}
|
|
569
|
-
function
|
|
553
|
+
function createTelegramOperatorDedupe(options = {}) {
|
|
570
554
|
const windowMs = options.windowMs ?? 600000;
|
|
571
555
|
const maxPerWindow = options.maxPerWindow ?? 20;
|
|
572
556
|
const fingerprint = options.fingerprint ?? defaultFingerprint;
|
|
@@ -610,6 +594,38 @@ function dedupeWindow(options) {
|
|
|
610
594
|
`) };
|
|
611
595
|
};
|
|
612
596
|
}
|
|
597
|
+
|
|
598
|
+
// src/telegram/operator-channel.ts
|
|
599
|
+
var MASK = "[redacted]";
|
|
600
|
+
function masked(text, patterns) {
|
|
601
|
+
let output = text;
|
|
602
|
+
for (const pattern of patterns) {
|
|
603
|
+
output = output.replace(new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`), MASK);
|
|
604
|
+
}
|
|
605
|
+
return output;
|
|
606
|
+
}
|
|
607
|
+
function literal(value) {
|
|
608
|
+
return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
609
|
+
}
|
|
610
|
+
function timer(milliseconds, signal) {
|
|
611
|
+
return new Promise((resolve, reject) => {
|
|
612
|
+
if (signal.aborted)
|
|
613
|
+
return reject(signal.reason);
|
|
614
|
+
const handle = setTimeout(() => {
|
|
615
|
+
signal.removeEventListener("abort", stop);
|
|
616
|
+
resolve();
|
|
617
|
+
}, milliseconds);
|
|
618
|
+
const stop = () => {
|
|
619
|
+
clearTimeout(handle);
|
|
620
|
+
reject(signal.reason);
|
|
621
|
+
};
|
|
622
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function fitted(text) {
|
|
626
|
+
const points = Array.from(text);
|
|
627
|
+
return points.length <= TELEGRAM_TEXT_LIMIT2 ? text : `${points.slice(0, TELEGRAM_TEXT_LIMIT2 - 1).join("")}…`;
|
|
628
|
+
}
|
|
613
629
|
function createTelegramOperatorChannel(config) {
|
|
614
630
|
const maxQueued = config.maxQueued ?? 200;
|
|
615
631
|
const minIntervalMs = config.minIntervalMs ?? 3000;
|
|
@@ -620,7 +636,7 @@ function createTelegramOperatorChannel(config) {
|
|
|
620
636
|
...config.sensitivePatterns ?? [],
|
|
621
637
|
...[...config.sensitiveValues ?? []].filter((value) => value !== "").sort((left, right) => right.length - left.length).map(literal)
|
|
622
638
|
];
|
|
623
|
-
const admit = config.dedupe ?
|
|
639
|
+
const admit = config.dedupe ? createTelegramOperatorDedupe(config.dedupe) : undefined;
|
|
624
640
|
const queue = [];
|
|
625
641
|
const idle = new Set;
|
|
626
642
|
let lifetime = new AbortController;
|
|
@@ -809,6 +825,9 @@ function parsed(body) {
|
|
|
809
825
|
return;
|
|
810
826
|
}
|
|
811
827
|
}
|
|
828
|
+
function handlerError(error) {
|
|
829
|
+
return error instanceof Error && error.name === "BotError" && "ctx" in error && "error" in error ? error.error : error;
|
|
830
|
+
}
|
|
812
831
|
function positive(name, value) {
|
|
813
832
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
814
833
|
throw new RangeError(`[stitchkit] telegram update intake: ${name} is a positive integer`);
|
|
@@ -862,7 +881,8 @@ function createTelegramUpdateIntake(config) {
|
|
|
862
881
|
try {
|
|
863
882
|
await config.handle(transportResult(update));
|
|
864
883
|
settlement = { state: "completed", at: now() };
|
|
865
|
-
} catch (
|
|
884
|
+
} catch (thrown) {
|
|
885
|
+
const error = handlerError(thrown);
|
|
866
886
|
const delay = number >= maxAttempts ? false : retry(error, number);
|
|
867
887
|
const message = error instanceof Error ? error.message : String(error);
|
|
868
888
|
settlement = delay === false ? { state: "abandoned", at: now(), error: message } : { state: "failed", at: now(), retryAt: now() + delay, error: message };
|
|
@@ -1022,9 +1042,210 @@ function memoryTelegramUpdateStore() {
|
|
|
1022
1042
|
}
|
|
1023
1043
|
};
|
|
1024
1044
|
}
|
|
1025
|
-
// src/telegram/update-store-
|
|
1045
|
+
// src/telegram/update-store-check.ts
|
|
1046
|
+
function same(actual, expected) {
|
|
1047
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
1048
|
+
}
|
|
1049
|
+
var claim = { maxAttempts: 2 };
|
|
1050
|
+
var RULES = {
|
|
1051
|
+
"one record per update_id": async (store, violated) => {
|
|
1052
|
+
if (!await store.add({ updateId: 7, body: "b", receivedAt: 0 }))
|
|
1053
|
+
violated("add of a new update returned false");
|
|
1054
|
+
if (await store.add({ updateId: 7, body: "other", receivedAt: 1 }))
|
|
1055
|
+
violated("add of a recorded update_id returned true");
|
|
1056
|
+
},
|
|
1057
|
+
"a live lease is not taken, a lapsed one is": async (store, violated) => {
|
|
1058
|
+
await store.add({ updateId: 7, body: "b", receivedAt: 0 });
|
|
1059
|
+
if (await store.claim(7, { ...claim, now: 10, leaseUntil: 100 }) !== 1)
|
|
1060
|
+
violated("the first claim was not attempt 1");
|
|
1061
|
+
if (await store.claim(7, { ...claim, now: 50, leaseUntil: 150 }) !== undefined)
|
|
1062
|
+
violated("a claim took an update under a live lease");
|
|
1063
|
+
if (!same(await store.due({ now: 50, pendingBefore: 50, limit: 10 }), []))
|
|
1064
|
+
violated("due listed an update under a live lease");
|
|
1065
|
+
if (!same(await store.due({ now: 101, pendingBefore: 0, limit: 10 }), [
|
|
1066
|
+
{ updateId: 7, body: "b" }
|
|
1067
|
+
]))
|
|
1068
|
+
violated("due did not list an update whose lease lapsed, with its body");
|
|
1069
|
+
if (await store.claim(7, { ...claim, now: 101, leaseUntil: 200 }) !== 2)
|
|
1070
|
+
violated("a lapsed lease was not taken as attempt 2");
|
|
1071
|
+
},
|
|
1072
|
+
"only the attempt holding the update renews or settles it": async (store, violated) => {
|
|
1073
|
+
await store.add({ updateId: 1, body: "b", receivedAt: 0 });
|
|
1074
|
+
await store.claim(1, { ...claim, now: 0, leaseUntil: 10 });
|
|
1075
|
+
await store.claim(1, { ...claim, now: 11, leaseUntil: 50 });
|
|
1076
|
+
if (await store.renew(1, 1, 500))
|
|
1077
|
+
violated("an attempt that lost its lease renewed it");
|
|
1078
|
+
if (!await store.renew(1, 2, 60))
|
|
1079
|
+
violated("the holding attempt could not renew");
|
|
1080
|
+
await store.settle(1, 1, { state: "completed", at: 20 });
|
|
1081
|
+
if ((await store.due({ now: 61, pendingBefore: 0, limit: 10 })).length !== 1)
|
|
1082
|
+
violated("an attempt that lost its lease settled the update");
|
|
1083
|
+
await store.settle(1, 2, { state: "completed", at: 30 });
|
|
1084
|
+
if (!same(await store.due({ now: 1000, pendingBefore: 1000, limit: 10 }), []))
|
|
1085
|
+
violated("a completed update is still due");
|
|
1086
|
+
},
|
|
1087
|
+
"a failed update is due at its retry, spent attempts abandon it": async (store, violated) => {
|
|
1088
|
+
await store.add({ updateId: 2, body: "b", receivedAt: 0 });
|
|
1089
|
+
await store.add({ updateId: 1, body: "a", receivedAt: 5 });
|
|
1090
|
+
await store.claim(2, { ...claim, now: 0, leaseUntil: 10 });
|
|
1091
|
+
await store.settle(2, 1, { state: "failed", at: 1, retryAt: 30, error: "x" });
|
|
1092
|
+
if (!same(await store.due({ now: 29, pendingBefore: 0, limit: 10 }), []))
|
|
1093
|
+
violated("a failed update was due before its retry, or a pending one before its grace");
|
|
1094
|
+
const due = await store.due({ now: 30, pendingBefore: 5, limit: 10 });
|
|
1095
|
+
if (!same(due.map((row) => row.updateId), [1, 2]))
|
|
1096
|
+
violated("due did not list a retry and a pending update in update_id order");
|
|
1097
|
+
if ((await store.due({ now: 30, pendingBefore: 5, limit: 1 })).length !== 1)
|
|
1098
|
+
violated("due ignored its limit");
|
|
1099
|
+
await store.claim(2, { ...claim, now: 30, leaseUntil: 40 });
|
|
1100
|
+
await store.settle(2, 2, { state: "failed", at: 31, retryAt: 35, error: "x" });
|
|
1101
|
+
if (await store.claim(2, { ...claim, now: 36, leaseUntil: 50 }) !== undefined)
|
|
1102
|
+
violated("an update past maxAttempts was claimed");
|
|
1103
|
+
if (!same(await store.due({ now: 100, pendingBefore: 0, limit: 10 }), []))
|
|
1104
|
+
violated("an update past maxAttempts was not abandoned");
|
|
1105
|
+
},
|
|
1106
|
+
"settled updates are forgotten after they are pruned": async (store, violated) => {
|
|
1107
|
+
await store.add({ updateId: 3, body: "b", receivedAt: 0 });
|
|
1108
|
+
await store.claim(3, { ...claim, now: 0, leaseUntil: 10 });
|
|
1109
|
+
await store.settle(3, 1, { state: "abandoned", at: 36, error: "x" });
|
|
1110
|
+
await store.add({ updateId: 4, body: "b", receivedAt: 0 });
|
|
1111
|
+
if (await store.prune(36) !== 0)
|
|
1112
|
+
violated("prune forgot an update settled at its bound");
|
|
1113
|
+
if (await store.prune(37) !== 1)
|
|
1114
|
+
violated("prune did not forget exactly the settled update before its bound");
|
|
1115
|
+
if (!await store.add({ updateId: 3, body: "b", receivedAt: 40 }))
|
|
1116
|
+
violated("a pruned update_id could not be recorded again");
|
|
1117
|
+
if (await store.add({ updateId: 4, body: "b", receivedAt: 40 }))
|
|
1118
|
+
violated("prune forgot an update that was never settled");
|
|
1119
|
+
},
|
|
1120
|
+
"of concurrent claims exactly one takes the update": async (store, violated) => {
|
|
1121
|
+
await store.add({ updateId: 9, body: "b", receivedAt: 0 });
|
|
1122
|
+
const taken = await Promise.all(Array.from({ length: 8 }, () => store.claim(9, { maxAttempts: 5, now: 1, leaseUntil: 100 })));
|
|
1123
|
+
if (taken.filter((attempt) => attempt !== undefined).length !== 1)
|
|
1124
|
+
violated(`${taken.filter((attempt) => attempt !== undefined).length} of 8 concurrent claims took one update`);
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
async function checkTelegramUpdateStore(make) {
|
|
1128
|
+
const violations = [];
|
|
1129
|
+
for (const [name, rule] of Object.entries(RULES)) {
|
|
1130
|
+
try {
|
|
1131
|
+
await rule(await make(), (what) => violations.push(`${name}: ${what}`));
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
violations.push(`${name}: threw ${error instanceof Error ? error.message : String(error)}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return violations;
|
|
1137
|
+
}
|
|
1138
|
+
// src/telegram/update-store-postgres.ts
|
|
1026
1139
|
var TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
1027
|
-
|
|
1140
|
+
function checkedTable(table) {
|
|
1141
|
+
if (!TABLE_NAME.test(table)) {
|
|
1142
|
+
throw new TypeError("[stitchkit] telegram update store: a table name is [A-Za-z_][A-Za-z0-9_]*");
|
|
1143
|
+
}
|
|
1144
|
+
return table;
|
|
1145
|
+
}
|
|
1146
|
+
function postgresTelegramUpdateStoreSchema(table = "telegram_updates") {
|
|
1147
|
+
const name = checkedTable(table);
|
|
1148
|
+
return `CREATE TABLE IF NOT EXISTS ${name} (
|
|
1149
|
+
update_id BIGINT PRIMARY KEY,
|
|
1150
|
+
body TEXT NOT NULL,
|
|
1151
|
+
state TEXT NOT NULL,
|
|
1152
|
+
attempts INTEGER NOT NULL,
|
|
1153
|
+
due_at BIGINT NOT NULL,
|
|
1154
|
+
received_at BIGINT NOT NULL,
|
|
1155
|
+
settled_at BIGINT,
|
|
1156
|
+
error TEXT
|
|
1157
|
+
);
|
|
1158
|
+
CREATE INDEX IF NOT EXISTS ${name}_due ON ${name} (state, due_at);`;
|
|
1159
|
+
}
|
|
1160
|
+
var CLAIMABLE = `(state = 'pending' OR (state = 'failed' AND due_at <= $1::bigint)
|
|
1161
|
+
OR (state = 'processing' AND due_at < $1::bigint))`;
|
|
1162
|
+
function integer(value) {
|
|
1163
|
+
if (typeof value === "number")
|
|
1164
|
+
return value;
|
|
1165
|
+
if (typeof value === "bigint" || typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
1166
|
+
return Number(value);
|
|
1167
|
+
}
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
function field(row, name) {
|
|
1171
|
+
return typeof row === "object" && row !== null && name in row ? Reflect.get(row, name) : undefined;
|
|
1172
|
+
}
|
|
1173
|
+
function postgresTelegramUpdateStore(config) {
|
|
1174
|
+
const table = checkedTable(config.table ?? "telegram_updates");
|
|
1175
|
+
const { query } = config;
|
|
1176
|
+
let ready;
|
|
1177
|
+
const run = async (text, parameters) => {
|
|
1178
|
+
if (config.createTable !== false) {
|
|
1179
|
+
ready ??= postgresTelegramUpdateStoreSchema(table).split(";").map((statement) => statement.trim()).filter((statement) => statement !== "").reduce((previous, statement) => previous.then(() => query(statement, [])), Promise.resolve()).then(() => {
|
|
1180
|
+
return;
|
|
1181
|
+
}, (error) => {
|
|
1182
|
+
ready = undefined;
|
|
1183
|
+
throw error;
|
|
1184
|
+
});
|
|
1185
|
+
await ready;
|
|
1186
|
+
}
|
|
1187
|
+
return query(text, parameters);
|
|
1188
|
+
};
|
|
1189
|
+
return {
|
|
1190
|
+
async add(update) {
|
|
1191
|
+
const rows = await run(`INSERT INTO ${table} (update_id, body, state, attempts, due_at, received_at)
|
|
1192
|
+
VALUES ($1::bigint, $2::text, 'pending', 0, $3::bigint, $3::bigint)
|
|
1193
|
+
ON CONFLICT (update_id) DO NOTHING RETURNING update_id`, [update.updateId, update.body, update.receivedAt]);
|
|
1194
|
+
return rows.length > 0;
|
|
1195
|
+
},
|
|
1196
|
+
async claim(updateId, options) {
|
|
1197
|
+
const [row] = await run(`UPDATE ${table} SET state = 'processing', attempts = attempts + 1,
|
|
1198
|
+
due_at = $2::bigint, error = NULL
|
|
1199
|
+
WHERE update_id = $3::bigint AND attempts < $4::integer AND ${CLAIMABLE}
|
|
1200
|
+
RETURNING attempts`, [options.now, options.leaseUntil, updateId, options.maxAttempts]);
|
|
1201
|
+
const attempts = integer(field(row, "attempts"));
|
|
1202
|
+
if (attempts !== undefined)
|
|
1203
|
+
return attempts;
|
|
1204
|
+
await run(`UPDATE ${table} SET state = 'abandoned', settled_at = $1::bigint, error = 'attempts spent'
|
|
1205
|
+
WHERE update_id = $2::bigint AND attempts >= $3::integer AND ${CLAIMABLE}
|
|
1206
|
+
RETURNING update_id`, [options.now, updateId, options.maxAttempts]);
|
|
1207
|
+
return;
|
|
1208
|
+
},
|
|
1209
|
+
async renew(updateId, attempt, leaseUntil) {
|
|
1210
|
+
const rows = await run(`UPDATE ${table} SET due_at = $1::bigint
|
|
1211
|
+
WHERE update_id = $2::bigint AND attempts = $3::integer AND state = 'processing'
|
|
1212
|
+
RETURNING update_id`, [leaseUntil, updateId, attempt]);
|
|
1213
|
+
return rows.length > 0;
|
|
1214
|
+
},
|
|
1215
|
+
async settle(updateId, attempt, settlement) {
|
|
1216
|
+
await run(`UPDATE ${table} SET state = $1::text, due_at = COALESCE($2::bigint, due_at),
|
|
1217
|
+
settled_at = $3::bigint, error = $4::text
|
|
1218
|
+
WHERE update_id = $5::bigint AND attempts = $6::integer AND state = 'processing'
|
|
1219
|
+
RETURNING update_id`, [
|
|
1220
|
+
settlement.state,
|
|
1221
|
+
settlement.state === "failed" ? settlement.retryAt : null,
|
|
1222
|
+
settlement.state === "failed" ? null : settlement.at,
|
|
1223
|
+
settlement.state === "completed" ? null : settlement.error,
|
|
1224
|
+
updateId,
|
|
1225
|
+
attempt
|
|
1226
|
+
]);
|
|
1227
|
+
},
|
|
1228
|
+
async due(options) {
|
|
1229
|
+
const rows = await run(`SELECT update_id, body FROM ${table}
|
|
1230
|
+
WHERE (state = 'pending' AND received_at <= $2::bigint)
|
|
1231
|
+
OR (state <> 'pending' AND ${CLAIMABLE})
|
|
1232
|
+
ORDER BY update_id LIMIT $3::integer`, [options.now, options.pendingBefore, options.limit]);
|
|
1233
|
+
return rows.flatMap((row) => {
|
|
1234
|
+
const updateId = integer(field(row, "update_id"));
|
|
1235
|
+
const body = field(row, "body");
|
|
1236
|
+
return updateId !== undefined && typeof body === "string" ? [{ updateId, body }] : [];
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
async prune(before) {
|
|
1240
|
+
const rows = await run(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < $1::bigint
|
|
1241
|
+
RETURNING update_id`, [before]);
|
|
1242
|
+
return rows.length;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
// src/telegram/update-store-sqlite.ts
|
|
1247
|
+
var TABLE_NAME2 = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
1248
|
+
var CLAIMABLE2 = `(state = 'pending' OR (state = 'failed' AND due_at <= ?1)
|
|
1028
1249
|
OR (state = 'processing' AND due_at < ?1))`;
|
|
1029
1250
|
function rowOf(row) {
|
|
1030
1251
|
if (typeof row !== "object" || row === null)
|
|
@@ -1035,7 +1256,7 @@ function rowOf(row) {
|
|
|
1035
1256
|
}
|
|
1036
1257
|
function sqliteTelegramUpdateStore(config) {
|
|
1037
1258
|
const table = config.table ?? "telegram_updates";
|
|
1038
|
-
if (!
|
|
1259
|
+
if (!TABLE_NAME2.test(table)) {
|
|
1039
1260
|
throw new TypeError("[stitchkit] telegram update store: a table name is [A-Za-z_][A-Za-z0-9_]*");
|
|
1040
1261
|
}
|
|
1041
1262
|
const database = config.database;
|
|
@@ -1053,14 +1274,14 @@ function sqliteTelegramUpdateStore(config) {
|
|
|
1053
1274
|
const insert = database.prepare(`INSERT OR IGNORE INTO ${table} (update_id, body, state, attempts, due_at, received_at)
|
|
1054
1275
|
VALUES (?1, ?2, 'pending', 0, ?3, ?3)`);
|
|
1055
1276
|
const take = database.prepare(`UPDATE ${table} SET state = 'processing', attempts = attempts + 1, due_at = ?2, error = NULL
|
|
1056
|
-
WHERE update_id = ?3 AND attempts < ?4 AND ${
|
|
1277
|
+
WHERE update_id = ?3 AND attempts < ?4 AND ${CLAIMABLE2} RETURNING attempts`);
|
|
1057
1278
|
const spend = database.prepare(`UPDATE ${table} SET state = 'abandoned', settled_at = ?1, error = 'attempts spent'
|
|
1058
|
-
WHERE update_id = ?2 AND attempts >= ?3 AND ${
|
|
1279
|
+
WHERE update_id = ?2 AND attempts >= ?3 AND ${CLAIMABLE2}`);
|
|
1059
1280
|
const renew = database.prepare(`UPDATE ${table} SET due_at = ?1 WHERE update_id = ?2 AND attempts = ?3 AND state = 'processing'`);
|
|
1060
1281
|
const settle = database.prepare(`UPDATE ${table} SET state = ?1, due_at = COALESCE(?2, due_at), settled_at = ?3, error = ?4
|
|
1061
1282
|
WHERE update_id = ?5 AND attempts = ?6 AND state = 'processing'`);
|
|
1062
1283
|
const due = database.prepare(`SELECT update_id, body FROM ${table}
|
|
1063
|
-
WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${
|
|
1284
|
+
WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${CLAIMABLE2})
|
|
1064
1285
|
ORDER BY update_id LIMIT ?3`);
|
|
1065
1286
|
const prune = database.prepare(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < ?1`);
|
|
1066
1287
|
return {
|
|
@@ -1199,19 +1420,26 @@ async function receiveTelegramWebhook(request, options) {
|
|
|
1199
1420
|
}
|
|
1200
1421
|
export {
|
|
1201
1422
|
TELEGRAM_BOT_TOKEN_PATTERN,
|
|
1423
|
+
TELEGRAM_SEND_FAILURE_REASONS2 as TELEGRAM_SEND_FAILURE_REASONS,
|
|
1202
1424
|
TELEGRAM_WEBHOOK_NONE,
|
|
1203
1425
|
TelegramBotApiError,
|
|
1204
1426
|
TelegramLocalFileError,
|
|
1205
1427
|
TelegramWebhookClaimError,
|
|
1206
1428
|
callTelegramBotApi,
|
|
1429
|
+
checkTelegramUpdateStore,
|
|
1207
1430
|
checkTelegramWebhook,
|
|
1208
1431
|
claimTelegramWebhook,
|
|
1209
1432
|
classifyTelegramSendFailure2 as classifyTelegramSendFailure,
|
|
1210
1433
|
createTelegramLocalFiles,
|
|
1211
1434
|
createTelegramOperatorChannel,
|
|
1435
|
+
createTelegramOperatorDedupe,
|
|
1212
1436
|
createTelegramUpdateIntake,
|
|
1213
1437
|
memoryTelegramUpdateStore,
|
|
1438
|
+
parseTelegramUser,
|
|
1439
|
+
postgresTelegramUpdateStore,
|
|
1440
|
+
postgresTelegramUpdateStoreSchema,
|
|
1214
1441
|
receiveTelegramWebhook,
|
|
1442
|
+
redactTelegramBotToken,
|
|
1215
1443
|
runTelegramBroadcast,
|
|
1216
1444
|
sqliteTelegramUpdateStore,
|
|
1217
1445
|
telegramBroadcastSender,
|
|
@@ -57,9 +57,10 @@ import {
|
|
|
57
57
|
defineContract2
|
|
58
58
|
} from "../index-ck71tbcf.js";
|
|
59
59
|
import"../index-1bx1xv1k.js";
|
|
60
|
+
import"../index-sznqf8fx.js";
|
|
60
61
|
import {
|
|
61
62
|
redact2
|
|
62
|
-
} from "../index-
|
|
63
|
+
} from "../index-e6c2spsd.js";
|
|
63
64
|
import {
|
|
64
65
|
AsyncOperationCancelResultSchema2,
|
|
65
66
|
createAsyncOperationSnapshotSchema2,
|
|
@@ -14,16 +14,20 @@ var BY_DESCRIPTION = [
|
|
|
14
14
|
"message-invalid"
|
|
15
15
|
]
|
|
16
16
|
];
|
|
17
|
-
var
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
var TRAITS = {
|
|
18
|
+
"blocked-by-user": { retryable: false, recipientUnreachable: true },
|
|
19
|
+
"user-deactivated": { retryable: false, recipientUnreachable: true },
|
|
20
|
+
"chat-not-found": { retryable: false, recipientUnreachable: true },
|
|
21
|
+
"not-started": { retryable: false, recipientUnreachable: true },
|
|
22
|
+
"rate-limited": { retryable: true, recipientUnreachable: false },
|
|
23
|
+
"message-invalid": { retryable: false, recipientUnreachable: false },
|
|
24
|
+
"server-error": { retryable: true, recipientUnreachable: false },
|
|
25
|
+
unknown: { retryable: false, recipientUnreachable: false }
|
|
26
|
+
};
|
|
27
|
+
function isSendFailureReason(value) {
|
|
28
|
+
return Object.hasOwn(TRAITS, value);
|
|
29
|
+
}
|
|
30
|
+
var TELEGRAM_SEND_FAILURE_REASONS2 = Object.keys(TRAITS).filter(isSendFailureReason);
|
|
27
31
|
function numberAt(value, keys) {
|
|
28
32
|
if (!isRecord(value))
|
|
29
33
|
return;
|
|
@@ -85,8 +89,7 @@ function classifyTelegramSendFailure2(error) {
|
|
|
85
89
|
return {
|
|
86
90
|
reason,
|
|
87
91
|
...status !== undefined && { status },
|
|
88
|
-
|
|
89
|
-
recipientUnreachable: UNREACHABLE.has(reason),
|
|
92
|
+
...TRAITS[reason],
|
|
90
93
|
evidence: "description"
|
|
91
94
|
};
|
|
92
95
|
}
|
|
@@ -138,4 +141,4 @@ function classifyTelegramEditRefusal(error) {
|
|
|
138
141
|
return "other";
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
export { classifyTelegramSendFailure2, classifyTelegramEditRefusal };
|
|
144
|
+
export { TELEGRAM_SEND_FAILURE_REASONS2, classifyTelegramSendFailure2, classifyTelegramEditRefusal };
|