@prajwalshete/telegram-plugin 0.0.0-stage → 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.
- package/CHANGELOG.md +87 -0
- package/LICENSE +21 -0
- package/README.md +109 -2
- package/dist/adapters/clock.d.ts +3 -0
- package/dist/adapters/clock.js +4 -0
- package/dist/adapters/console-log.d.ts +13 -0
- package/dist/adapters/console-log.js +41 -0
- package/dist/adapters/d1-db.d.ts +5 -0
- package/dist/adapters/d1-db.js +37 -0
- package/dist/adapters/ids.d.ts +5 -0
- package/dist/adapters/ids.js +25 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/keyring.d.ts +50 -0
- package/dist/adapters/keyring.js +174 -0
- package/dist/adapters/metrics.d.ts +28 -0
- package/dist/adapters/metrics.js +48 -0
- package/dist/adapters/r2-files.d.ts +20 -0
- package/dist/adapters/r2-files.js +53 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +45 -0
- package/dist/client/broadcasts.d.ts +35 -0
- package/dist/client/commands.d.ts +23 -0
- package/dist/client/endpoint.d.ts +34 -0
- package/dist/client/events.d.ts +33 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.js +3 -0
- package/dist/client/schedules.d.ts +42 -0
- package/dist/client/schema.d.ts +181 -0
- package/dist/client/send.d.ts +15 -0
- package/dist/client/templates.d.ts +47 -0
- package/dist/contracts/bots.d.ts +129 -0
- package/dist/contracts/bots.js +70 -0
- package/dist/contracts/broadcasts.d.ts +116 -0
- package/dist/contracts/broadcasts.js +63 -0
- package/dist/contracts/chats.d.ts +97 -0
- package/dist/contracts/chats.js +66 -0
- package/dist/contracts/commands.d.ts +52 -0
- package/dist/contracts/commands.js +53 -0
- package/dist/contracts/common.d.ts +38 -0
- package/dist/contracts/common.js +41 -0
- package/dist/contracts/deliveries.d.ts +103 -0
- package/dist/contracts/deliveries.js +74 -0
- package/dist/contracts/destinations.d.ts +24 -0
- package/dist/contracts/destinations.js +28 -0
- package/dist/contracts/events.d.ts +165 -0
- package/dist/contracts/events.js +98 -0
- package/dist/contracts/features.d.ts +20 -0
- package/dist/contracts/features.js +21 -0
- package/dist/contracts/health.d.ts +55 -0
- package/dist/contracts/health.js +58 -0
- package/dist/contracts/index.d.ts +22 -0
- package/dist/contracts/index.js +22 -0
- package/dist/contracts/labels.d.ts +38 -0
- package/dist/contracts/labels.js +52 -0
- package/dist/contracts/links.d.ts +51 -0
- package/dist/contracts/links.js +50 -0
- package/dist/contracts/meta.d.ts +132 -0
- package/dist/contracts/meta.js +85 -0
- package/dist/contracts/permissions.d.ts +24 -0
- package/dist/contracts/permissions.js +27 -0
- package/dist/contracts/problems.d.ts +106 -0
- package/dist/contracts/problems.js +101 -0
- package/dist/contracts/recipients.d.ts +36 -0
- package/dist/contracts/recipients.js +22 -0
- package/dist/contracts/schedules.d.ts +276 -0
- package/dist/contracts/schedules.js +143 -0
- package/dist/contracts/send.d.ts +126 -0
- package/dist/contracts/send.js +69 -0
- package/dist/contracts/subscriptions.d.ts +38 -0
- package/dist/contracts/subscriptions.js +31 -0
- package/dist/contracts/templates.d.ts +358 -0
- package/dist/contracts/templates.js +174 -0
- package/dist/contracts/users.d.ts +15 -0
- package/dist/contracts/users.js +19 -0
- package/dist/core/guards.d.ts +22 -0
- package/dist/core/guards.js +47 -0
- package/dist/core/html/escape.d.ts +2 -0
- package/dist/core/html/escape.js +36 -0
- package/dist/core/html/tokenize.js +65 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/pacing.d.ts +1 -0
- package/dist/core/pacing.js +86 -0
- package/dist/core/random.d.ts +2 -0
- package/dist/core/random.js +38 -0
- package/dist/core/recipients.d.ts +1 -0
- package/dist/core/recipients.js +86 -0
- package/dist/core/schedule/describe.d.ts +3 -0
- package/dist/core/schedule/describe.js +74 -0
- package/dist/core/schedule/index.d.ts +4 -0
- package/dist/core/schedule/policy.d.ts +1 -0
- package/dist/core/schedule/policy.js +37 -0
- package/dist/core/schedule/rule.d.ts +25 -0
- package/dist/core/schedule/rule.js +142 -0
- package/dist/core/schedule/tz.d.ts +2 -0
- package/dist/core/schedule/tz.js +59 -0
- package/dist/core/split/plain.d.ts +2 -0
- package/dist/core/split/plain.js +23 -0
- package/dist/core/split/split.d.ts +15 -0
- package/dist/core/split/split.js +158 -0
- package/dist/core/template/ast.d.ts +58 -0
- package/dist/core/template/ast.js +5 -0
- package/dist/core/template/expr.js +108 -0
- package/dist/core/template/filters.d.ts +20 -0
- package/dist/core/template/filters.js +127 -0
- package/dist/core/template/format.d.ts +17 -0
- package/dist/core/template/format.js +88 -0
- package/dist/core/template/index.d.ts +20 -0
- package/dist/core/template/index.js +29 -0
- package/dist/core/template/parse.d.ts +8 -0
- package/dist/core/template/parse.js +369 -0
- package/dist/core/template/render.d.ts +17 -0
- package/dist/core/template/render.js +118 -0
- package/dist/core/template/shape.d.ts +28 -0
- package/dist/core/template/shape.js +151 -0
- package/dist/core/template/tags.js +105 -0
- package/dist/core/template/validate.d.ts +10 -0
- package/dist/core/template/validate.js +144 -0
- package/dist/core/template/variables.d.ts +15 -0
- package/dist/core/template/variables.js +41 -0
- package/dist/do/index.d.ts +3 -0
- package/dist/do/index.js +3 -0
- package/dist/do/telegram-bot-do.d.ts +45 -0
- package/dist/do/telegram-bot-do.js +219 -0
- package/dist/engine/actions/callback.js +256 -0
- package/dist/engine/actions/tokens.d.ts +2 -0
- package/dist/engine/actions/tokens.js +51 -0
- package/dist/engine/bot-api.js +20 -0
- package/dist/engine/bot-features.js +39 -0
- package/dist/engine/chat-state.js +66 -0
- package/dist/engine/content/audience.js +54 -0
- package/dist/engine/content/buttons.d.ts +1 -0
- package/dist/engine/content/buttons.js +113 -0
- package/dist/engine/content/fanout.js +284 -0
- package/dist/engine/content/intersect.d.ts +6 -0
- package/dist/engine/content/intersect.js +26 -0
- package/dist/engine/content/providers.d.ts +6 -0
- package/dist/engine/content/providers.js +142 -0
- package/dist/engine/content/recipients.js +128 -0
- package/dist/engine/content/render.js +138 -0
- package/dist/engine/content/reply.js +156 -0
- package/dist/engine/content/report-files.js +78 -0
- package/dist/engine/content/template-rows.js +74 -0
- package/dist/engine/dialogs/dialog.d.ts +2 -0
- package/dist/engine/dialogs/dialog.js +375 -0
- package/dist/engine/dispatch/builtins.js +198 -0
- package/dist/engine/dispatch/commands.js +194 -0
- package/dist/engine/dispatch/context.d.ts +2 -0
- package/dist/engine/dispatch/context.js +194 -0
- package/dist/engine/dispatch/dispatcher.js +66 -0
- package/dist/engine/dispatch/handlers.d.ts +1 -0
- package/dist/engine/dispatch/handlers.js +103 -0
- package/dist/engine/dispatch/lifecycle.js +85 -0
- package/dist/engine/dispatch/link-chat.js +31 -0
- package/dist/engine/dispatch/linking.js +273 -0
- package/dist/engine/errors.d.ts +60 -0
- package/dist/engine/errors.js +72 -0
- package/dist/engine/events/filters.d.ts +5 -0
- package/dist/engine/events/filters.js +82 -0
- package/dist/engine/events/kick.js +30 -0
- package/dist/engine/events/payload.d.ts +3 -0
- package/dist/engine/events/payload.js +16 -0
- package/dist/engine/events/tick.js +208 -0
- package/dist/engine/i18n.d.ts +12 -0
- package/dist/engine/i18n.js +55 -0
- package/dist/engine/inbound/files.js +121 -0
- package/dist/engine/inbound/handler.js +46 -0
- package/dist/engine/jobs.d.ts +32 -0
- package/dist/engine/jobs.js +85 -0
- package/dist/engine/link-codes.js +46 -0
- package/dist/engine/metrics.js +12 -0
- package/dist/engine/options.d.ts +369 -0
- package/dist/engine/options.js +81 -0
- package/dist/engine/outbox/dead-letter.js +78 -0
- package/dist/engine/outbox/do-registry.d.ts +10 -0
- package/dist/engine/outbox/do-registry.js +10 -0
- package/dist/engine/outbox/do-transport.d.ts +33 -0
- package/dist/engine/outbox/do-transport.js +73 -0
- package/dist/engine/outbox/drain.d.ts +13 -0
- package/dist/engine/outbox/drain.js +381 -0
- package/dist/engine/outbox/enqueue.d.ts +1 -0
- package/dist/engine/outbox/enqueue.js +63 -0
- package/dist/engine/outbox/select.js +24 -0
- package/dist/engine/outbox/send.js +362 -0
- package/dist/engine/outbox/transport.d.ts +35 -0
- package/dist/engine/outbox/transport.js +175 -0
- package/dist/engine/outbox/wake.js +28 -0
- package/dist/engine/registry.d.ts +502 -0
- package/dist/engine/registry.js +258 -0
- package/dist/engine/rows.d.ts +25 -0
- package/dist/engine/rows.js +131 -0
- package/dist/engine/runtime.d.ts +95 -0
- package/dist/engine/runtime.js +149 -0
- package/dist/engine/schedules/fire.js +208 -0
- package/dist/engine/schedules/resume.js +75 -0
- package/dist/engine/schedules/rows.js +69 -0
- package/dist/engine/schedules/tick.js +30 -0
- package/dist/engine/schedules/wake.js +50 -0
- package/dist/engine/state.d.ts +11 -0
- package/dist/engine/state.js +78 -0
- package/dist/locales/en.js +339 -0
- package/dist/locales/en.json +341 -0
- package/dist/locales/mr.js +339 -0
- package/dist/locales/mr.json +341 -0
- package/dist/ports/db.d.ts +22 -0
- package/dist/ports/index.d.ts +4 -0
- package/dist/ports/ports.d.ts +257 -0
- package/dist/ports/sql.d.ts +39 -0
- package/dist/ports/sql.js +76 -0
- package/dist/renderers/csv.d.ts +14 -0
- package/dist/renderers/csv.js +51 -0
- package/dist/renderers/html.d.ts +44 -0
- package/dist/renderers/html.js +81 -0
- package/dist/renderers/index.d.ts +5 -0
- package/dist/renderers/index.js +4 -0
- package/dist/renderers/table.d.ts +30 -0
- package/dist/renderers/table.js +114 -0
- package/dist/routes/admin.js +146 -0
- package/dist/routes/app.d.ts +3111 -0
- package/dist/routes/app.js +9 -0
- package/dist/routes/broadcasts.js +37 -0
- package/dist/routes/commands.js +35 -0
- package/dist/routes/content.js +12 -0
- package/dist/routes/events.js +36 -0
- package/dist/routes/http.d.ts +30 -0
- package/dist/routes/http.js +223 -0
- package/dist/routes/me.js +59 -0
- package/dist/routes/schedules.js +62 -0
- package/dist/routes/send.js +24 -0
- package/dist/routes/templates.js +70 -0
- package/dist/routes/webhook.js +147 -0
- package/dist/schema/columns.js +307 -0
- package/dist/schema/ddl.d.ts +7 -0
- package/dist/schema/ddl.js +45 -0
- package/dist/schema/index.d.ts +4255 -0
- package/dist/schema/index.js +44 -0
- package/dist/schema/reference.sql +359 -0
- package/dist/schema/tables.d.ts +4243 -0
- package/dist/schema/tables.js +327 -0
- package/dist/schema/version.d.ts +5 -0
- package/dist/schema/version.js +7 -0
- package/dist/server/index.d.ts +55 -0
- package/dist/server/index.js +46 -0
- package/dist/server/plugin.d.ts +81 -0
- package/dist/server/plugin.js +247 -0
- package/dist/server/version.d.ts +2 -0
- package/dist/server/version.js +4 -0
- package/dist/services/ai-tool.d.ts +26 -0
- package/dist/services/ai-tool.js +148 -0
- package/dist/services/bots.js +300 -0
- package/dist/services/broadcasts.js +390 -0
- package/dist/services/chats.js +83 -0
- package/dist/services/cleanup.js +83 -0
- package/dist/services/commands.js +140 -0
- package/dist/services/deliveries.js +53 -0
- package/dist/services/destinations.js +91 -0
- package/dist/services/event-rules.js +229 -0
- package/dist/services/events.js +20 -0
- package/dist/services/idempotency.js +64 -0
- package/dist/services/links.js +200 -0
- package/dist/services/menu.js +95 -0
- package/dist/services/ops.js +90 -0
- package/dist/services/rekey.js +63 -0
- package/dist/services/reports.js +87 -0
- package/dist/services/schedules.js +214 -0
- package/dist/services/send.d.ts +14 -0
- package/dist/services/send.js +421 -0
- package/dist/services/subscriptions.js +28 -0
- package/dist/services/templates.js +259 -0
- package/dist/telegram/classify.d.ts +30 -0
- package/dist/telegram/classify.js +164 -0
- package/dist/telegram/client.d.ts +73 -0
- package/dist/telegram/client.js +135 -0
- package/dist/telegram/errors.d.ts +37 -0
- package/dist/telegram/errors.js +62 -0
- package/dist/telegram/inbound.d.ts +40 -0
- package/dist/telegram/inbound.js +72 -0
- package/dist/telegram/index.d.ts +8 -0
- package/dist/telegram/multipart.d.ts +5 -0
- package/dist/telegram/multipart.js +47 -0
- package/dist/telegram/types.d.ts +271 -0
- package/dist/telegram/update.d.ts +833 -0
- package/dist/telegram/update.js +129 -0
- package/dist/testing/fake-html.d.ts +9 -0
- package/dist/testing/fake-html.js +83 -0
- package/dist/testing/fake-telegram.d.ts +206 -0
- package/dist/testing/fake-telegram.js +856 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/reference-sql.d.ts +4 -0
- package/dist/testing/reference-sql.js +8 -0
- package/dist/testing/test-plugin.d.ts +126 -0
- package/dist/testing/test-plugin.js +182 -0
- package/dist/testing/updates.d.ts +100 -0
- package/dist/testing/updates.js +212 -0
- package/package.json +113 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/telegram/update.ts
|
|
3
|
+
/** Bot API `User` (loose: unknown keys pass through). */
|
|
4
|
+
const TgUser = z.looseObject({
|
|
5
|
+
id: z.number().int(),
|
|
6
|
+
is_bot: z.boolean(),
|
|
7
|
+
first_name: z.string(),
|
|
8
|
+
last_name: z.string().optional(),
|
|
9
|
+
username: z.string().optional(),
|
|
10
|
+
language_code: z.string().optional()
|
|
11
|
+
});
|
|
12
|
+
/** Bot API `Chat` (loose). */
|
|
13
|
+
const TgChat = z.looseObject({
|
|
14
|
+
id: z.number().int(),
|
|
15
|
+
/** `private` | `group` | `supergroup` | `channel` (open for future types). */
|
|
16
|
+
type: z.string(),
|
|
17
|
+
title: z.string().optional(),
|
|
18
|
+
username: z.string().optional(),
|
|
19
|
+
first_name: z.string().optional(),
|
|
20
|
+
last_name: z.string().optional(),
|
|
21
|
+
is_forum: z.boolean().optional()
|
|
22
|
+
});
|
|
23
|
+
/** Bot API `MessageEntity` (offsets/lengths in UTF-16 code units). */
|
|
24
|
+
const TgMessageEntity = z.looseObject({
|
|
25
|
+
type: z.string(),
|
|
26
|
+
offset: z.number().int(),
|
|
27
|
+
length: z.number().int(),
|
|
28
|
+
url: z.string().optional(),
|
|
29
|
+
user: TgUser.optional(),
|
|
30
|
+
language: z.string().optional(),
|
|
31
|
+
custom_emoji_id: z.string().optional()
|
|
32
|
+
});
|
|
33
|
+
/** Bot API `PhotoSize` (one size of a photo). */
|
|
34
|
+
const TgPhotoSize = z.looseObject({
|
|
35
|
+
file_id: z.string(),
|
|
36
|
+
file_unique_id: z.string(),
|
|
37
|
+
width: z.number().int(),
|
|
38
|
+
height: z.number().int(),
|
|
39
|
+
file_size: z.number().int().optional()
|
|
40
|
+
});
|
|
41
|
+
/** Bot API `Document` (`file_size` in bytes). */
|
|
42
|
+
const TgDocument = z.looseObject({
|
|
43
|
+
file_id: z.string(),
|
|
44
|
+
file_unique_id: z.string(),
|
|
45
|
+
file_name: z.string().optional(),
|
|
46
|
+
mime_type: z.string().optional(),
|
|
47
|
+
file_size: z.number().int().optional()
|
|
48
|
+
});
|
|
49
|
+
/** Bot API `ForumTopicCreated` service message. */
|
|
50
|
+
const TgForumTopicCreated = z.looseObject({
|
|
51
|
+
name: z.string(),
|
|
52
|
+
icon_color: z.number().int().optional(),
|
|
53
|
+
icon_custom_emoji_id: z.string().optional()
|
|
54
|
+
});
|
|
55
|
+
/** Message (also used for MaybeInaccessibleMessage: `date: 0` means the bot can no longer read it). */
|
|
56
|
+
const TgMessage = z.looseObject({
|
|
57
|
+
message_id: z.number().int(),
|
|
58
|
+
/** Unix seconds. */
|
|
59
|
+
date: z.number().int(),
|
|
60
|
+
chat: TgChat,
|
|
61
|
+
from: TgUser.optional(),
|
|
62
|
+
sender_chat: TgChat.optional(),
|
|
63
|
+
message_thread_id: z.number().int().optional(),
|
|
64
|
+
is_topic_message: z.boolean().optional(),
|
|
65
|
+
text: z.string().optional(),
|
|
66
|
+
entities: z.array(TgMessageEntity).optional(),
|
|
67
|
+
caption: z.string().optional(),
|
|
68
|
+
caption_entities: z.array(TgMessageEntity).optional(),
|
|
69
|
+
photo: z.array(TgPhotoSize).optional(),
|
|
70
|
+
document: TgDocument.optional(),
|
|
71
|
+
/** Only the fields a reply check needs; the nested message is not parsed further. */
|
|
72
|
+
reply_to_message: z.looseObject({
|
|
73
|
+
message_id: z.number().int(),
|
|
74
|
+
from: TgUser.optional()
|
|
75
|
+
}).optional(),
|
|
76
|
+
migrate_to_chat_id: z.number().int().optional(),
|
|
77
|
+
migrate_from_chat_id: z.number().int().optional(),
|
|
78
|
+
new_chat_members: z.array(TgUser).optional(),
|
|
79
|
+
left_chat_member: TgUser.optional(),
|
|
80
|
+
group_chat_created: z.literal(true).optional(),
|
|
81
|
+
supergroup_chat_created: z.literal(true).optional(),
|
|
82
|
+
forum_topic_created: TgForumTopicCreated.optional()
|
|
83
|
+
});
|
|
84
|
+
/** Bot API `CallbackQuery` (an inline button tap; `data` ≤ 64 bytes). */
|
|
85
|
+
const TgCallbackQuery = z.looseObject({
|
|
86
|
+
id: z.string(),
|
|
87
|
+
from: TgUser,
|
|
88
|
+
chat_instance: z.string(),
|
|
89
|
+
message: TgMessage.optional(),
|
|
90
|
+
inline_message_id: z.string().optional(),
|
|
91
|
+
data: z.string().optional()
|
|
92
|
+
});
|
|
93
|
+
/** `creator` | `administrator` | `member` | `restricted` | `left` | `kicked`. */
|
|
94
|
+
const TgChatMember = z.looseObject({
|
|
95
|
+
status: z.string(),
|
|
96
|
+
user: TgUser
|
|
97
|
+
});
|
|
98
|
+
/** Bot API `ChatMemberUpdated` (`my_chat_member` / `chat_member`); `date` is Unix seconds. */
|
|
99
|
+
const TgChatMemberUpdated = z.looseObject({
|
|
100
|
+
chat: TgChat,
|
|
101
|
+
from: TgUser,
|
|
102
|
+
date: z.number().int(),
|
|
103
|
+
old_chat_member: TgChatMember,
|
|
104
|
+
new_chat_member: TgChatMember
|
|
105
|
+
});
|
|
106
|
+
/** Bot API `Update`, only the update types the plugin handles (others parse but are ignored). */
|
|
107
|
+
const TgUpdate = z.looseObject({
|
|
108
|
+
update_id: z.number().int(),
|
|
109
|
+
message: TgMessage.optional(),
|
|
110
|
+
edited_message: TgMessage.optional(),
|
|
111
|
+
callback_query: TgCallbackQuery.optional(),
|
|
112
|
+
my_chat_member: TgChatMemberUpdated.optional(),
|
|
113
|
+
chat_member: TgChatMemberUpdated.optional()
|
|
114
|
+
});
|
|
115
|
+
/** Validates an inbound webhook / getUpdates body. Never throws; `issues` hold paths only (no message text). */
|
|
116
|
+
function parseUpdate(input) {
|
|
117
|
+
const r = TgUpdate.safeParse(input);
|
|
118
|
+
if (r.success) return {
|
|
119
|
+
ok: true,
|
|
120
|
+
update: r.data
|
|
121
|
+
};
|
|
122
|
+
const raw = typeof input === "object" && input !== null ? input.update_id : void 0;
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
updateId: typeof raw === "number" && Number.isInteger(raw) ? raw : null,
|
|
126
|
+
issues: r.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.code}`)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export { TgCallbackQuery, TgChat, TgChatMember, TgChatMemberUpdated, TgDocument, TgForumTopicCreated, TgMessage, TgMessageEntity, TgPhotoSize, TgUpdate, TgUser, parseUpdate };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Result of {@link checkTelegramHtml}. */
|
|
2
|
+
export interface HtmlCheck {
|
|
3
|
+
/** Plain text after entity parsing (what Telegram counts and returns). */
|
|
4
|
+
text: string;
|
|
5
|
+
/** Telegram-style error description, or null when valid. */
|
|
6
|
+
error: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** Validates HTML the way Telegram's `parse_mode: HTML` does (allowed tags, nesting, entities); returns its visible text. */
|
|
9
|
+
export declare function checkTelegramHtml(html: string): HtmlCheck;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/testing/fake-html.ts
|
|
2
|
+
const ALLOWED = /* @__PURE__ */ new Set([
|
|
3
|
+
"b",
|
|
4
|
+
"strong",
|
|
5
|
+
"i",
|
|
6
|
+
"em",
|
|
7
|
+
"u",
|
|
8
|
+
"ins",
|
|
9
|
+
"s",
|
|
10
|
+
"strike",
|
|
11
|
+
"del",
|
|
12
|
+
"span",
|
|
13
|
+
"tg-spoiler",
|
|
14
|
+
"a",
|
|
15
|
+
"code",
|
|
16
|
+
"pre",
|
|
17
|
+
"blockquote",
|
|
18
|
+
"tg-emoji",
|
|
19
|
+
"tg-time"
|
|
20
|
+
]);
|
|
21
|
+
const NAMED = /* @__PURE__ */ new Map([
|
|
22
|
+
["lt", "<"],
|
|
23
|
+
["gt", ">"],
|
|
24
|
+
["amp", "&"],
|
|
25
|
+
["quot", "\""]
|
|
26
|
+
]);
|
|
27
|
+
const TOKEN_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)>|<|&(#x[0-9A-Fa-f]+|#[0-9]+|[A-Za-z]+);/g;
|
|
28
|
+
function byteOffset(s, index) {
|
|
29
|
+
return new TextEncoder().encode(s.slice(0, index)).length;
|
|
30
|
+
}
|
|
31
|
+
/** Validates HTML the way Telegram's `parse_mode: HTML` does (allowed tags, nesting, entities); returns its visible text. */
|
|
32
|
+
function checkTelegramHtml(html) {
|
|
33
|
+
const stack = [];
|
|
34
|
+
let text = "";
|
|
35
|
+
let last = 0;
|
|
36
|
+
for (const m of html.matchAll(TOKEN_RE)) {
|
|
37
|
+
const idx = m.index;
|
|
38
|
+
text += html.slice(last, idx);
|
|
39
|
+
last = idx + m[0].length;
|
|
40
|
+
const [whole, closing, rawTag, , entity] = m;
|
|
41
|
+
if (entity !== void 0) {
|
|
42
|
+
if (entity.startsWith("#")) {
|
|
43
|
+
const code = entity.startsWith("#x") ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1));
|
|
44
|
+
text += String.fromCodePoint(code);
|
|
45
|
+
} else {
|
|
46
|
+
const ch = NAMED.get(entity);
|
|
47
|
+
if (ch === void 0) return {
|
|
48
|
+
text,
|
|
49
|
+
error: `Bad Request: can't parse entities: Unsupported HTML entity "&${entity};"`
|
|
50
|
+
};
|
|
51
|
+
text += ch;
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (whole === "<" || rawTag === void 0) return {
|
|
56
|
+
text,
|
|
57
|
+
error: `Bad Request: can't parse entities: Unsupported start tag "" at byte offset ${byteOffset(html, idx)}`
|
|
58
|
+
};
|
|
59
|
+
const tag = rawTag.toLowerCase();
|
|
60
|
+
if (!ALLOWED.has(tag)) return {
|
|
61
|
+
text,
|
|
62
|
+
error: `Bad Request: can't parse entities: Unsupported ${closing ? "end" : "start"} tag "${tag}" at byte offset ${byteOffset(html, idx)}`
|
|
63
|
+
};
|
|
64
|
+
if (closing) {
|
|
65
|
+
const open = stack.at(-1);
|
|
66
|
+
if (open !== tag) return {
|
|
67
|
+
text,
|
|
68
|
+
error: `Bad Request: can't parse entities: Unmatched end tag at byte offset ${byteOffset(html, idx)}, expected "</${open ?? ""}>", found "</${tag}>"`
|
|
69
|
+
};
|
|
70
|
+
stack.pop();
|
|
71
|
+
} else stack.push(tag);
|
|
72
|
+
}
|
|
73
|
+
text += html.slice(last);
|
|
74
|
+
if (stack.length > 0) return {
|
|
75
|
+
text,
|
|
76
|
+
error: `Bad Request: can't parse entities: Can't find end tag corresponding to start tag "${stack.at(-1)}"`
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
text,
|
|
80
|
+
error: null
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export { checkTelegramHtml };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { TgUpdate } from "../telegram/update.js";
|
|
2
|
+
import { BotCommand } from "../telegram/types.js";
|
|
3
|
+
type Json = Record<string, unknown>;
|
|
4
|
+
/** Options for {@link FakeTelegram.addBot}; omitted fields get generated values. */
|
|
5
|
+
export interface FakeBotInit {
|
|
6
|
+
token?: string;
|
|
7
|
+
id?: number;
|
|
8
|
+
username?: string;
|
|
9
|
+
firstName?: string;
|
|
10
|
+
}
|
|
11
|
+
/** A fake bot's identity (its token works against `fake.fetch`). */
|
|
12
|
+
export interface FakeBotInfo {
|
|
13
|
+
token: string;
|
|
14
|
+
id: number;
|
|
15
|
+
username: string;
|
|
16
|
+
firstName: string;
|
|
17
|
+
}
|
|
18
|
+
/** Selects a bot by token, username or id. Default: the first bot. */
|
|
19
|
+
export type BotRef = string | number | undefined;
|
|
20
|
+
/** How a fake chat treats the bot: `blocked`/`deactivated`/`not_started`/`kicked`/`left` make sends fail with Telegram's real 403 texts. */
|
|
21
|
+
export type FakeChatStatus = "member" | "administrator" | "blocked" | "deactivated" | "not_started" | "kicked" | "left";
|
|
22
|
+
/** Options for {@link FakeTelegram.setChat}. */
|
|
23
|
+
export interface FakeChatInit {
|
|
24
|
+
type?: "private" | "group" | "supergroup" | "channel";
|
|
25
|
+
title?: string;
|
|
26
|
+
username?: string;
|
|
27
|
+
firstName?: string;
|
|
28
|
+
isForum?: boolean;
|
|
29
|
+
/** Existing forum topic ids (sends to other thread ids fail when `isForum`). */
|
|
30
|
+
topics?: number[];
|
|
31
|
+
status?: FakeChatStatus;
|
|
32
|
+
/** Group was upgraded: sends fail with 400 + `migrate_to_chat_id`. */
|
|
33
|
+
migratedTo?: number;
|
|
34
|
+
/** `getChatMember` statuses by user id. */
|
|
35
|
+
members?: Record<number, string>;
|
|
36
|
+
}
|
|
37
|
+
interface ChatState {
|
|
38
|
+
id: number;
|
|
39
|
+
type: "private" | "group" | "supergroup" | "channel";
|
|
40
|
+
title?: string | undefined;
|
|
41
|
+
username?: string | undefined;
|
|
42
|
+
firstName?: string | undefined;
|
|
43
|
+
isForum: boolean;
|
|
44
|
+
topics: Set<number>;
|
|
45
|
+
status: FakeChatStatus;
|
|
46
|
+
migratedTo?: number | undefined;
|
|
47
|
+
members: Map<number, string>;
|
|
48
|
+
lastSendAt?: number;
|
|
49
|
+
recentSends: number[];
|
|
50
|
+
nextMessageId: number;
|
|
51
|
+
}
|
|
52
|
+
/** A failure injected with {@link FakeTelegram.failNext}. */
|
|
53
|
+
export interface FakeFailure {
|
|
54
|
+
/** HTTP status and `error_code` (default 400). */
|
|
55
|
+
status?: number;
|
|
56
|
+
description?: string;
|
|
57
|
+
/** Seconds, sent as `parameters.retry_after`. */
|
|
58
|
+
retryAfter?: number;
|
|
59
|
+
migrateToChatId?: number;
|
|
60
|
+
/** Throw like a failed fetch (the error text contains the URL, to prove the client drops it). */
|
|
61
|
+
network?: boolean;
|
|
62
|
+
/** Never answer; resolves only when the request is aborted (timeout tests). */
|
|
63
|
+
hang?: boolean;
|
|
64
|
+
/** How many calls fail (default 1). */
|
|
65
|
+
times?: number;
|
|
66
|
+
/** Only for this bot. */
|
|
67
|
+
bot?: BotRef;
|
|
68
|
+
}
|
|
69
|
+
/** One recorded Bot API call (`at` = epoch ms from the fake clock). */
|
|
70
|
+
export interface FakeCall {
|
|
71
|
+
botId: number;
|
|
72
|
+
method: string;
|
|
73
|
+
params: Json;
|
|
74
|
+
ok: boolean;
|
|
75
|
+
errorCode?: number;
|
|
76
|
+
at: number;
|
|
77
|
+
}
|
|
78
|
+
/** A text message the fake accepted (`chatId` as text, `at` epoch ms). */
|
|
79
|
+
export interface FakeSentMessage {
|
|
80
|
+
botId: number;
|
|
81
|
+
chatId: string;
|
|
82
|
+
messageId: number;
|
|
83
|
+
threadId: number | undefined;
|
|
84
|
+
/** As sent (HTML when `parseMode` is HTML). */
|
|
85
|
+
text: string;
|
|
86
|
+
/** Latest text after edits. */
|
|
87
|
+
currentText: string;
|
|
88
|
+
parseMode: string | undefined;
|
|
89
|
+
replyMarkup: unknown;
|
|
90
|
+
params: Json;
|
|
91
|
+
edits: number;
|
|
92
|
+
deleted: boolean;
|
|
93
|
+
at: number;
|
|
94
|
+
}
|
|
95
|
+
/** A document or photo the fake accepted (`size` in bytes). */
|
|
96
|
+
export interface FakeSentFile {
|
|
97
|
+
botId: number;
|
|
98
|
+
chatId: string;
|
|
99
|
+
messageId: number;
|
|
100
|
+
kind: "document" | "photo";
|
|
101
|
+
fileId: string;
|
|
102
|
+
fileName: string;
|
|
103
|
+
mime: string;
|
|
104
|
+
size: number;
|
|
105
|
+
/** Uploaded bytes (null when sent by file_id / URL). */
|
|
106
|
+
bytes: Uint8Array | null;
|
|
107
|
+
caption: string | undefined;
|
|
108
|
+
/** Uploaded with multipart (false: file_id or URL). */
|
|
109
|
+
uploaded: boolean;
|
|
110
|
+
params: Json;
|
|
111
|
+
at: number;
|
|
112
|
+
}
|
|
113
|
+
/** The webhook registered with `setWebhook`. */
|
|
114
|
+
export interface FakeWebhook {
|
|
115
|
+
url: string;
|
|
116
|
+
secretToken: string | undefined;
|
|
117
|
+
allowedUpdates: string[] | undefined;
|
|
118
|
+
maxConnections: number;
|
|
119
|
+
dropPendingUpdates: boolean;
|
|
120
|
+
}
|
|
121
|
+
/** Options for `new FakeTelegram()`. */
|
|
122
|
+
export interface FakeTelegramOptions {
|
|
123
|
+
/** Epoch ms clock (for `date` fields and rate limits). */
|
|
124
|
+
now?: () => number;
|
|
125
|
+
/** Return 429 when a chat gets more than 1 msg/s (private) or 20 msg/min (groups). */
|
|
126
|
+
rateLimit?: boolean;
|
|
127
|
+
/** Unknown chats give "chat not found" instead of being created on first send. */
|
|
128
|
+
strictChats?: boolean;
|
|
129
|
+
/** Create no default bot. */
|
|
130
|
+
noDefaultBot?: boolean;
|
|
131
|
+
}
|
|
132
|
+
/** In-memory Telegram Bot API. */
|
|
133
|
+
export declare class FakeTelegram {
|
|
134
|
+
#private;
|
|
135
|
+
/** Every call, in order. */
|
|
136
|
+
readonly calls: FakeCall[];
|
|
137
|
+
/** Every text message sent. */
|
|
138
|
+
readonly messages: FakeSentMessage[];
|
|
139
|
+
/** Every document and photo sent. */
|
|
140
|
+
readonly files: FakeSentFile[];
|
|
141
|
+
/** Every `answerCallbackQuery`. */
|
|
142
|
+
readonly callbackAnswers: {
|
|
143
|
+
botId: number;
|
|
144
|
+
params: Json;
|
|
145
|
+
}[];
|
|
146
|
+
/** Every `sendChatAction`. */
|
|
147
|
+
readonly chatActions: {
|
|
148
|
+
botId: number;
|
|
149
|
+
chatId: string;
|
|
150
|
+
action: string;
|
|
151
|
+
}[];
|
|
152
|
+
constructor(opts?: FakeTelegramOptions);
|
|
153
|
+
/** Use as `fetchTelegram({ fetch: fake.fetch })`. */
|
|
154
|
+
readonly fetch: (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
|
|
155
|
+
/** The first bot (created by default). */
|
|
156
|
+
get defaultBot(): FakeBotInfo;
|
|
157
|
+
/** All bots, in creation order. */
|
|
158
|
+
get bots(): FakeBotInfo[];
|
|
159
|
+
/** Adds a bot; returns its token, id and username. */
|
|
160
|
+
addBot(init?: FakeBotInit): FakeBotInfo;
|
|
161
|
+
/** Revokes a token (later calls get 401), like `/revoke` in BotFather. */
|
|
162
|
+
revokeToken(bot?: BotRef): void;
|
|
163
|
+
/** Creates or changes a chat known to a bot. */
|
|
164
|
+
setChat(chatId: number, init?: FakeChatInit, bot?: BotRef): void;
|
|
165
|
+
/** Current state of a chat as the fake sees it, or undefined. */
|
|
166
|
+
chat(chatId: number | string, bot?: BotRef): Readonly<ChatState> | undefined;
|
|
167
|
+
/** The next matching call(s) fail. */
|
|
168
|
+
failNext(method: string, failure?: FakeFailure): void;
|
|
169
|
+
/** Stores a file the bot can `getFile` + download (for inbound photo / document tests). */
|
|
170
|
+
addFile(bytes: Uint8Array | string, o?: {
|
|
171
|
+
name?: string;
|
|
172
|
+
mime?: string;
|
|
173
|
+
bot?: BotRef;
|
|
174
|
+
}): string;
|
|
175
|
+
/** Queues an update for `getUpdates`. */
|
|
176
|
+
pushUpdate(update: TgUpdate, bot?: BotRef): void;
|
|
177
|
+
/** Updates queued for `getUpdates` and not yet confirmed. */
|
|
178
|
+
pendingUpdates(bot?: BotRef): readonly TgUpdate[];
|
|
179
|
+
/** The registered webhook, or null. */
|
|
180
|
+
webhook(bot?: BotRef): FakeWebhook | null;
|
|
181
|
+
/** Makes getWebhookInfo report a delivery error. */
|
|
182
|
+
setWebhookError(message: string | null, bot?: BotRef): void;
|
|
183
|
+
/** The POST Telegram would send to the registered webhook (with the secret header). */
|
|
184
|
+
webhookRequest(update: TgUpdate, o?: {
|
|
185
|
+
bot?: BotRef;
|
|
186
|
+
url?: string;
|
|
187
|
+
secret?: string | null;
|
|
188
|
+
}): Request;
|
|
189
|
+
/** Commands set for a scope + language (exact match, like getMyCommands). */
|
|
190
|
+
commands(o?: {
|
|
191
|
+
scope?: Json;
|
|
192
|
+
language?: string;
|
|
193
|
+
bot?: BotRef;
|
|
194
|
+
}): BotCommand[] | undefined;
|
|
195
|
+
/** Recorded calls to one method (case-insensitive). */
|
|
196
|
+
callsTo(method: string): FakeCall[];
|
|
197
|
+
/** Text messages sent to a chat (optionally by one bot). */
|
|
198
|
+
messagesTo(chatId: number | string, bot?: BotRef): FakeSentMessage[];
|
|
199
|
+
/** Documents sent to a chat. */
|
|
200
|
+
documentsTo(chatId: number | string, bot?: BotRef): FakeSentFile[];
|
|
201
|
+
/** Photos sent to a chat. */
|
|
202
|
+
photosTo(chatId: number | string, bot?: BotRef): FakeSentFile[];
|
|
203
|
+
/** Clears recorded calls, messages and failures (bots, chats and webhooks stay). */
|
|
204
|
+
reset(): void;
|
|
205
|
+
}
|
|
206
|
+
export {};
|