@yaebal/test 0.0.1 → 0.2.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/README.md +400 -15
  2. package/lib/api.d.ts +83 -0
  3. package/lib/api.d.ts.map +1 -0
  4. package/lib/api.js +186 -0
  5. package/lib/api.js.map +1 -0
  6. package/lib/api.test.d.ts +2 -0
  7. package/lib/api.test.d.ts.map +1 -0
  8. package/lib/api.test.js +131 -0
  9. package/lib/api.test.js.map +1 -0
  10. package/lib/bot-messages.d.ts +42 -0
  11. package/lib/bot-messages.d.ts.map +1 -0
  12. package/lib/bot-messages.js +72 -0
  13. package/lib/bot-messages.js.map +1 -0
  14. package/lib/chat-actor.d.ts +45 -0
  15. package/lib/chat-actor.d.ts.map +1 -0
  16. package/lib/chat-actor.js +72 -0
  17. package/lib/chat-actor.js.map +1 -0
  18. package/lib/clock.d.ts +22 -0
  19. package/lib/clock.d.ts.map +1 -0
  20. package/lib/clock.js +72 -0
  21. package/lib/clock.js.map +1 -0
  22. package/lib/clock.test.d.ts +2 -0
  23. package/lib/clock.test.d.ts.map +1 -0
  24. package/lib/clock.test.js +69 -0
  25. package/lib/clock.test.js.map +1 -0
  26. package/lib/env.d.ts +100 -0
  27. package/lib/env.d.ts.map +1 -0
  28. package/lib/env.js +164 -0
  29. package/lib/env.js.map +1 -0
  30. package/lib/env.test.d.ts +2 -0
  31. package/lib/env.test.d.ts.map +1 -0
  32. package/lib/env.test.js +302 -0
  33. package/lib/env.test.js.map +1 -0
  34. package/lib/fetch.d.ts +3 -0
  35. package/lib/fetch.d.ts.map +1 -0
  36. package/lib/fetch.js +12 -0
  37. package/lib/fetch.js.map +1 -0
  38. package/lib/index.d.ts +19 -52
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +19 -115
  41. package/lib/index.js.map +1 -1
  42. package/lib/internal.d.ts +25 -0
  43. package/lib/internal.d.ts.map +1 -0
  44. package/lib/internal.js +19 -0
  45. package/lib/internal.js.map +1 -0
  46. package/lib/keyboard.d.ts +15 -0
  47. package/lib/keyboard.d.ts.map +1 -0
  48. package/lib/keyboard.js +25 -0
  49. package/lib/keyboard.js.map +1 -0
  50. package/lib/keyboard.test.d.ts +2 -0
  51. package/lib/keyboard.test.d.ts.map +1 -0
  52. package/lib/keyboard.test.js +31 -0
  53. package/lib/keyboard.test.js.map +1 -0
  54. package/lib/normalize.d.ts +10 -0
  55. package/lib/normalize.d.ts.map +1 -0
  56. package/lib/normalize.js +27 -0
  57. package/lib/normalize.js.map +1 -0
  58. package/lib/reactions.d.ts +3 -0
  59. package/lib/reactions.d.ts.map +1 -0
  60. package/lib/reactions.js +17 -0
  61. package/lib/reactions.js.map +1 -0
  62. package/lib/updates.d.ts +126 -0
  63. package/lib/updates.d.ts.map +1 -0
  64. package/lib/updates.js +200 -0
  65. package/lib/updates.js.map +1 -0
  66. package/lib/updates.test.d.ts +2 -0
  67. package/lib/updates.test.d.ts.map +1 -0
  68. package/lib/updates.test.js +72 -0
  69. package/lib/updates.test.js.map +1 -0
  70. package/lib/user-actor.d.ts +188 -0
  71. package/lib/user-actor.d.ts.map +1 -0
  72. package/lib/user-actor.js +465 -0
  73. package/lib/user-actor.js.map +1 -0
  74. package/lib/webhook.d.ts +18 -0
  75. package/lib/webhook.d.ts.map +1 -0
  76. package/lib/webhook.js +25 -0
  77. package/lib/webhook.js.map +1 -0
  78. package/lib/webhook.test.d.ts +2 -0
  79. package/lib/webhook.test.d.ts.map +1 -0
  80. package/lib/webhook.test.js +36 -0
  81. package/lib/webhook.test.js.map +1 -0
  82. package/package.json +7 -5
  83. package/src/api.test.ts +180 -0
  84. package/src/api.ts +289 -0
  85. package/src/bot-messages.ts +117 -0
  86. package/src/chat-actor.ts +101 -0
  87. package/src/clock.test.ts +80 -0
  88. package/src/clock.ts +118 -0
  89. package/src/env.test.ts +370 -0
  90. package/src/env.ts +235 -0
  91. package/src/fetch.ts +11 -0
  92. package/src/index.ts +79 -169
  93. package/src/internal.ts +43 -0
  94. package/src/keyboard.test.ts +35 -0
  95. package/src/keyboard.ts +38 -0
  96. package/src/normalize.ts +34 -0
  97. package/src/reactions.ts +18 -0
  98. package/src/updates.test.ts +107 -0
  99. package/src/updates.ts +354 -0
  100. package/src/user-actor.ts +702 -0
  101. package/src/webhook.test.ts +54 -0
  102. package/src/webhook.ts +48 -0
  103. package/lib/index.test.d.ts +0 -2
  104. package/lib/index.test.d.ts.map +0 -1
  105. package/lib/index.test.js +0 -66
  106. package/lib/index.test.js.map +0 -1
  107. package/src/index.test.ts +0 -101
package/lib/api.js ADDED
@@ -0,0 +1,186 @@
1
+ import { TelegramError, } from "@yaebal/core";
2
+ import { normalizeParams } from "./normalize.js";
3
+ /**
4
+ * simulate a real Telegram Bot API error response — the bot sees exactly what it would see
5
+ * from the real api: a thrown {@link TestApiError} (a {@link TelegramError} subclass).
6
+ *
7
+ * @example
8
+ * env.onApi("sendMessage", apiError(403, "Forbidden: bot was blocked by the user"));
9
+ * env.onApi("sendMessage", apiError(429, "Too Many Requests", { retry_after: 30 }));
10
+ */
11
+ export function apiError(code, description, parameters) {
12
+ return { __yaebalApiError: true, code, description, parameters };
13
+ }
14
+ /** typeguard for a stored `apiError(...)` sentinel. */
15
+ export function isApiErrorSentinel(value) {
16
+ return typeof value === "object" && value !== null && "__yaebalApiError" in value;
17
+ }
18
+ /** a {@link TelegramError} carrying the optional `parameters` bag (e.g. `retry_after`) real errors ship. */
19
+ export class TestApiError extends TelegramError {
20
+ parameters;
21
+ constructor(method, code, description, parameters) {
22
+ super(method, code, description);
23
+ this.parameters = parameters;
24
+ }
25
+ }
26
+ /** default results for known methods; everything else resolves to `{}` (or throws, with `strictApi`). */
27
+ const STUBBED_METHODS = new Set(["getMe", "answerCallbackQuery"]);
28
+ function builtinResult(method, nextMessageId) {
29
+ if (method.startsWith("send") || method === "copyMessage" || method === "forwardMessage") {
30
+ return { message_id: nextMessageId() };
31
+ }
32
+ if (method === "answerCallbackQuery")
33
+ return true;
34
+ if (method === "getMe")
35
+ return { id: 1, is_bot: true, first_name: "bot", username: "bot" };
36
+ return {};
37
+ }
38
+ function hasBuiltin(method) {
39
+ return (method.startsWith("send") ||
40
+ method === "copyMessage" ||
41
+ method === "forwardMessage" ||
42
+ STUBBED_METHODS.has(method));
43
+ }
44
+ /**
45
+ * a fake {@link Api} whose every method records `{ method, params, result | error, at }` into
46
+ * `calls` and resolves to a sensible default (auto-incrementing `message_id` for `send*`, `true`
47
+ * for `answerCallbackQuery`, `{}` otherwise) — or to whatever `onApi`/`options.results` says.
48
+ * `before`/`after`/`onError` are real hook registrars (not no-ops): register a hook the same way
49
+ * you would on the production `Api` and it actually runs, including retries requested by an
50
+ * `onError` hook. the mock never actually waits on a requested `delayMs` — retries settle
51
+ * instantly, so tests stay fast (pair with {@link installTestClock} if the code under test
52
+ * schedules the retry itself via `setTimeout`).
53
+ */
54
+ export function mockApi(options = {}) {
55
+ const calls = [];
56
+ const overrides = new Map();
57
+ const attempts = new Map();
58
+ const now = options.now ?? (() => Date.now());
59
+ let nextMessageId = 1;
60
+ for (const [method, reply] of Object.entries(options.results ?? {})) {
61
+ overrides.set(method, { queue: [], permanent: reply });
62
+ }
63
+ const hooks = {
64
+ before: [],
65
+ after: [],
66
+ onError: [],
67
+ };
68
+ function resolveReply(method, params) {
69
+ const attempt = (attempts.get(method) ?? 0) + 1;
70
+ attempts.set(method, attempt);
71
+ const override = overrides.get(method);
72
+ let reply;
73
+ if (override?.queue.length) {
74
+ const head = override.queue[0];
75
+ head.remaining--;
76
+ reply = head.reply;
77
+ if (head.remaining <= 0)
78
+ override.queue.shift();
79
+ }
80
+ else if (override && "permanent" in override) {
81
+ reply = override.permanent;
82
+ }
83
+ else if (options.strictApi && !hasBuiltin(method)) {
84
+ throw new Error(`mockApi: no stub for "${method}" — register one with onApi("${method}", ...), or disable strictApi`);
85
+ }
86
+ else {
87
+ return builtinResult(method, () => nextMessageId++);
88
+ }
89
+ if (typeof reply === "function") {
90
+ return reply(params, attempt);
91
+ }
92
+ if (isApiErrorSentinel(reply)) {
93
+ return new TestApiError(method, reply.code, reply.description, reply.parameters);
94
+ }
95
+ return reply;
96
+ }
97
+ const call = async (method, rawParams) => {
98
+ let p = normalizeParams(rawParams);
99
+ for (const hook of hooks.before) {
100
+ const next = await hook(method, p);
101
+ if (next !== undefined)
102
+ p = next;
103
+ }
104
+ for (let attempt = 1;; attempt++) {
105
+ let result;
106
+ try {
107
+ result = resolveReply(method, p);
108
+ if (result instanceof Error)
109
+ throw result;
110
+ }
111
+ catch (error) {
112
+ let retry;
113
+ for (const hook of hooks.onError) {
114
+ const action = await hook(method, error, attempt);
115
+ if (action?.retry) {
116
+ retry = action;
117
+ break;
118
+ }
119
+ }
120
+ calls.push({ method, params: p, error, at: now() });
121
+ if (!retry)
122
+ throw error;
123
+ continue; // the mock never actually waits on retry.delayMs
124
+ }
125
+ for (const hook of hooks.after) {
126
+ const next = await hook(method, p, result);
127
+ if (next !== undefined)
128
+ result = next;
129
+ }
130
+ calls.push({ method, params: p, result, at: now() });
131
+ return result;
132
+ }
133
+ };
134
+ const registrar = {
135
+ call: (method, params) => call(method, params),
136
+ fileUrl: (filePath) => `https://example.invalid/file/${filePath}`,
137
+ before(hook) {
138
+ hooks.before.push(hook);
139
+ return api;
140
+ },
141
+ after(hook) {
142
+ hooks.after.push(hook);
143
+ return api;
144
+ },
145
+ onError(hook) {
146
+ hooks.onError.push(hook);
147
+ return api;
148
+ },
149
+ };
150
+ const api = new Proxy(registrar, {
151
+ get(obj, prop) {
152
+ if (prop in obj)
153
+ return obj[prop];
154
+ const method = (params) => call(prop, params);
155
+ obj[prop] = method;
156
+ return method;
157
+ },
158
+ });
159
+ return {
160
+ api,
161
+ calls,
162
+ hooks,
163
+ lastCall: (method) => method ? [...calls].reverse().find((c) => c.method === method) : calls.at(-1),
164
+ callsTo: (method) => calls.filter((c) => c.method === method),
165
+ onApi: (method, reply, opts) => {
166
+ const entry = overrides.get(method) ?? { queue: [] };
167
+ if (opts?.times !== undefined)
168
+ entry.queue.push({ reply, remaining: opts.times });
169
+ else
170
+ entry.permanent = reply;
171
+ overrides.set(method, entry);
172
+ },
173
+ offApi: (method) => {
174
+ if (method)
175
+ overrides.delete(method);
176
+ else
177
+ overrides.clear();
178
+ },
179
+ reset: () => {
180
+ calls.length = 0;
181
+ attempts.clear();
182
+ nextMessageId = 1;
183
+ },
184
+ };
185
+ }
186
+ //# sourceMappingURL=api.js.map
package/lib/api.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,131 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { TelegramError } from "@yaebal/core";
4
+ import { apiError, isApiErrorSentinel, mockApi, TestApiError } from "./api.js";
5
+ test("mockApi records calls and resolves sensible defaults", async () => {
6
+ const { api, calls } = mockApi();
7
+ const sent = await api.sendMessage({ chat_id: 1, text: "hi" });
8
+ assert.deepEqual(sent, { message_id: 1 });
9
+ const answered = await api.answerCallbackQuery({ callback_query_id: "1" });
10
+ assert.equal(answered, true);
11
+ const viaCall = await api.call("setMyCommands", { commands: [] });
12
+ assert.deepEqual(viaCall, {});
13
+ assert.equal(calls.length, 3);
14
+ assert.equal(calls[0]?.method, "sendMessage");
15
+ assert.deepEqual(calls[0]?.params, { chat_id: 1, text: "hi" });
16
+ assert.deepEqual(calls[0]?.result, { message_id: 1 });
17
+ assert.equal(typeof calls[0]?.at, "number");
18
+ });
19
+ test("mockApi hook registrars are chainable", () => {
20
+ const { api } = mockApi();
21
+ assert.equal(api.before(() => undefined), api);
22
+ assert.equal(api.after((_m, _p, r) => r), api);
23
+ });
24
+ test("mockApi auto-increments message_id across send* calls", async () => {
25
+ const { api } = mockApi();
26
+ const first = await api.sendMessage({ chat_id: 1, text: "a" });
27
+ const second = await api.sendMessage({ chat_id: 1, text: "b" });
28
+ assert.deepEqual(first, { message_id: 1 });
29
+ assert.deepEqual(second, { message_id: 2 });
30
+ });
31
+ test("mockApi results: static override replaces the built-in default", async () => {
32
+ const { api } = mockApi({ results: { sendMessage: { message_id: 99 } } });
33
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 99 });
34
+ });
35
+ test("mockApi results: an Error value makes the call throw", async () => {
36
+ const { api } = mockApi({
37
+ results: { sendMessage: new TelegramError("sendMessage", 400, "Bad Request") },
38
+ });
39
+ await assert.rejects(api.sendMessage({ chat_id: 1 }), TelegramError);
40
+ });
41
+ test("mockApi results: a function sees params and a running attempt count", async () => {
42
+ const { api } = mockApi({
43
+ results: {
44
+ sendMessage: (params, attempt) => attempt <= 2
45
+ ? new TelegramError("sendMessage", 429, "retry after 0")
46
+ : { message_id: 1, echo: params?.text },
47
+ },
48
+ });
49
+ api.onError((_m, _e, attempt) => (attempt <= 2 ? { retry: true } : undefined));
50
+ const result = await api.sendMessage({ chat_id: 1, text: "hi" });
51
+ assert.deepEqual(result, { message_id: 1, echo: "hi" });
52
+ });
53
+ test("onApi sets a permanent override; reset() clears calls and counters but keeps it", async () => {
54
+ const { api, calls, onApi, reset } = mockApi();
55
+ await api.sendMessage({ chat_id: 1 });
56
+ onApi("sendMessage", { message_id: 42 });
57
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 42 });
58
+ assert.equal(calls.length, 2);
59
+ reset();
60
+ assert.equal(calls.length, 0);
61
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 42 });
62
+ });
63
+ test("onApi with { times } is one-shot, then falls back to the previous permanent reply", async () => {
64
+ const { api, onApi } = mockApi({ results: { sendMessage: { message_id: 1 } } });
65
+ onApi("sendMessage", apiError(429, "Too Many Requests"), { times: 1 });
66
+ await assert.rejects(api.sendMessage({ chat_id: 1 }), TestApiError);
67
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 1 });
68
+ });
69
+ test("onApi { times: 2 } is consumed over exactly two calls", async () => {
70
+ const { api, onApi } = mockApi();
71
+ onApi("getChat", { id: 1, type: "private", stale: true }, { times: 2 });
72
+ onApi("getChat", { id: 1, type: "private", stale: false });
73
+ assert.equal((await api.call("getChat")).stale, true);
74
+ assert.equal((await api.call("getChat")).stale, true);
75
+ assert.equal((await api.call("getChat")).stale, false);
76
+ });
77
+ test("offApi drops a method's override, or every override with no argument", async () => {
78
+ const { api, onApi, offApi } = mockApi();
79
+ onApi("sendMessage", { message_id: 99 });
80
+ offApi("sendMessage");
81
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 1 });
82
+ onApi("getMe", { id: 7, is_bot: true, first_name: "x" });
83
+ offApi();
84
+ assert.deepEqual(await api.getMe(), { id: 1, is_bot: true, first_name: "bot", username: "bot" });
85
+ });
86
+ test("apiError() produces a sentinel that throws a TestApiError carrying parameters", async () => {
87
+ const sentinel = apiError(429, "Too Many Requests", { retry_after: 30 });
88
+ assert.ok(isApiErrorSentinel(sentinel));
89
+ const { api } = mockApi({ results: { sendMessage: sentinel } });
90
+ await assert.rejects(api.sendMessage({ chat_id: 1 }), (error) => {
91
+ assert.ok(error instanceof TestApiError);
92
+ assert.ok(error instanceof TelegramError);
93
+ assert.equal(error.code, 429);
94
+ assert.deepEqual(error.parameters, { retry_after: 30 });
95
+ return true;
96
+ });
97
+ });
98
+ test("strictApi throws for methods with no builtin default and no override", async () => {
99
+ const { api } = mockApi({ strictApi: true });
100
+ await assert.rejects(api.call("getChatMember"), /no stub for "getChatMember"/);
101
+ assert.deepEqual(await api.sendMessage({ chat_id: 1 }), { message_id: 1 });
102
+ });
103
+ test("mockApi.lastCall and callsTo filter recorded calls", async () => {
104
+ const { api, lastCall, callsTo } = mockApi();
105
+ await api.sendMessage({ chat_id: 1, text: "a" });
106
+ await api.answerCallbackQuery({ callback_query_id: "1" });
107
+ await api.sendMessage({ chat_id: 1, text: "b" });
108
+ assert.equal(lastCall()?.method, "sendMessage");
109
+ assert.equal(lastCall("answerCallbackQuery")?.method, "answerCallbackQuery");
110
+ assert.equal(lastCall("sendMessage")?.params?.text, "b");
111
+ assert.equal(callsTo("sendMessage").length, 2);
112
+ });
113
+ test("mockApi hooks actually run: before rewrites params, after rewrites result", async () => {
114
+ const { api } = mockApi();
115
+ api.before((_method, params) => ({ ...params, injected: true }));
116
+ api.after((_method, _params, result) => ({ ...result, tagged: true }));
117
+ const result = await api.sendMessage({ chat_id: 1 });
118
+ assert.deepEqual(result, { message_id: 1, tagged: true });
119
+ });
120
+ test("reply_markup builder instances are normalized to plain JSON on recorded calls", async () => {
121
+ const { api, calls } = mockApi();
122
+ await api.sendMessage({
123
+ chat_id: 1,
124
+ text: "hi",
125
+ reply_markup: { toJSON: () => ({ inline_keyboard: [[{ text: "ok", callback_data: "ok" }]] }) },
126
+ });
127
+ assert.deepEqual(calls[0]?.params?.reply_markup, {
128
+ inline_keyboard: [[{ text: "ok", callback_data: "ok" }]],
129
+ });
130
+ });
131
+ //# sourceMappingURL=api.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.js","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/E,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;IACvE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IAEjC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE7B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAE9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IAClD,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1B,MAAM,CAAC,KAAK,CACX,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAC3B,GAAG,CACH,CAAC;IACF,MAAM,CAAC,KAAK,CACX,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAC3B,GAAG,CACH,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAE1B,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAEhE,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAE1E,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;IACvE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QACvB,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE;KAC9E,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;IACtF,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QACvB,OAAO,EAAE;YACR,WAAW,EAAE,CAAC,MAA2C,EAAE,OAAe,EAAE,EAAE,CAC7E,OAAO,IAAI,CAAC;gBACX,CAAC,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,eAAe,CAAC;gBACxD,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;SACzC;KACD,CAAC,CAAC;IAEH,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iFAAiF,EAAE,KAAK,IAAI,EAAE;IAClG,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IAE/C,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACtC,KAAK,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5E,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAE9B,KAAK,EAAE,CAAC;IACR,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;IACpG,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAEhF,KAAK,CAAC,aAAa,EAAE,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAEvE,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC;IACpE,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IAEjC,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACxE,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAE3D,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAqB,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAqB,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAqB,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;IACvF,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;IAEzC,KAAK,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,aAAa,CAAC,CAAC;IACtB,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IAE3E,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC;IACT,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;IAChG,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACzE,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAExC,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEhE,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAc,EAAE,EAAE;QACxE,MAAM,CAAC,EAAE,CAAC,KAAK,YAAY,YAAY,CAAC,CAAC;QACzC,MAAM,CAAC,EAAE,CAAC,KAAK,YAAY,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9B,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC;IACb,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;IACvF,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,6BAA6B,CAAC,CAAC;IAC/E,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;IACrE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IAE7C,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACjD,MAAM,GAAG,CAAC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAEjD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC7E,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;IAC5F,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAE1B,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjE,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAI,MAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEnF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAErD,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;IAChG,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IAEjC,MAAM,GAAG,CAAC,WAAW,CAAC;QACrB,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,IAAI;QACV,YAAY,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE;KAC9F,CAAC,CAAC;IAEH,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE;QAChD,eAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;KACxD,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Api, Chat, Message } from "@yaebal/core";
2
+ /** `Message`'s own `reply_markup` union — reused so a `BotMessage` structurally satisfies `Message`. */
3
+ type ReplyMarkup = NonNullable<Message["reply_markup"]>;
4
+ /**
5
+ * a live mirror of one message the bot sent — built from the outgoing `send*` params (so it's
6
+ * populated even against the builtin `{ message_id }` stub, no `onApi` override needed) and kept
7
+ * in sync in place as `editMessageText`/`editMessageCaption`/`editMessageReplyMarkup` calls land,
8
+ * so a reference captured before an edit still reflects it after.
9
+ */
10
+ export interface BotMessage {
11
+ message_id: number;
12
+ chat: {
13
+ id: number;
14
+ type: Chat["type"];
15
+ };
16
+ text?: string;
17
+ caption?: string;
18
+ reply_markup?: ReplyMarkup;
19
+ date: number;
20
+ }
21
+ export interface LastBotMessageQuery {
22
+ /** scope to a specific chat (an `{ id }`-shaped value — a `ChatActor` or `pmChat` both qualify). */
23
+ chat?: {
24
+ id: number;
25
+ };
26
+ /** only consider messages currently carrying a non-empty `inline_keyboard`. */
27
+ withReplyMarkup?: boolean;
28
+ /** arbitrary predicate over the call that produced (or last touched) the message. */
29
+ where?: (call: {
30
+ method: string;
31
+ params: Record<string, unknown> | undefined;
32
+ }) => boolean;
33
+ }
34
+ export interface BotMessageTracker {
35
+ lastBotMessage(query?: LastBotMessageQuery): BotMessage | undefined;
36
+ botMessage(chatId: number, messageId: number): BotMessage | undefined;
37
+ clear(): void;
38
+ }
39
+ /** install bot-message tracking on a mock `api` via its `after` hook. */
40
+ export declare function attachBotMessageTracking(api: Api, resolveChatType: (chatId: number) => Chat["type"]): BotMessageTracker;
41
+ export {};
42
+ //# sourceMappingURL=bot-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot-messages.d.ts","sourceRoot":"","sources":["../src/bot-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvD,wGAAwG;AACxG,KAAK,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IACnC,oGAAoG;IACpG,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACtB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qFAAqF;IACrF,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;KAAE,KAAK,OAAO,CAAC;CAC3F;AAED,MAAM,WAAW,iBAAiB;IACjC,cAAc,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,UAAU,GAAG,SAAS,CAAC;IACpE,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACtE,KAAK,IAAI,IAAI,CAAC;CACd;AAWD,yEAAyE;AACzE,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,GAAG,EACR,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,GAC/C,iBAAiB,CAoEnB"}
@@ -0,0 +1,72 @@
1
+ const SEND_LIKE = (method) => method.startsWith("send") || method === "forwardMessage" || method === "copyMessage";
2
+ function hasInlineKeyboard(markup) {
3
+ const withRows = markup;
4
+ const rows = withRows?.inline_keyboard;
5
+ return Array.isArray(rows) && rows.length > 0;
6
+ }
7
+ /** install bot-message tracking on a mock `api` via its `after` hook. */
8
+ export function attachBotMessageTracking(api, resolveChatType) {
9
+ const byKey = new Map();
10
+ const order = [];
11
+ const key = (chatId, messageId) => `${chatId}:${messageId}`;
12
+ api.after((method, params, result) => {
13
+ const p = params ?? {};
14
+ if (method.startsWith("edit")) {
15
+ const chatId = p.chat_id;
16
+ const messageId = p.message_id;
17
+ if (chatId === undefined || messageId === undefined)
18
+ return;
19
+ const entry = byKey.get(key(chatId, messageId));
20
+ if (!entry)
21
+ return;
22
+ if (method === "editMessageText" && typeof p.text === "string")
23
+ entry.text = p.text;
24
+ if (method === "editMessageCaption")
25
+ entry.caption = p.caption;
26
+ if ("reply_markup" in p)
27
+ entry.reply_markup = p.reply_markup;
28
+ entry.lastCall = { method, params: p };
29
+ return;
30
+ }
31
+ if (!SEND_LIKE(method))
32
+ return;
33
+ if (!result || typeof result !== "object" || !("message_id" in result))
34
+ return;
35
+ const chatId = p.chat_id;
36
+ if (chatId === undefined)
37
+ return;
38
+ const messageId = result.message_id;
39
+ const entry = {
40
+ message_id: messageId,
41
+ chat: { id: chatId, type: resolveChatType(chatId) },
42
+ text: p.text,
43
+ caption: p.caption,
44
+ reply_markup: p.reply_markup,
45
+ date: result.date ?? Date.now(),
46
+ lastCall: { method, params: p },
47
+ };
48
+ byKey.set(key(chatId, messageId), entry);
49
+ order.push(entry);
50
+ });
51
+ return {
52
+ lastBotMessage(query = {}) {
53
+ for (let i = order.length - 1; i >= 0; i--) {
54
+ const entry = order[i];
55
+ if (query.chat && entry.chat.id !== query.chat.id)
56
+ continue;
57
+ if (query.withReplyMarkup && !hasInlineKeyboard(entry.reply_markup))
58
+ continue;
59
+ if (query.where && !query.where(entry.lastCall))
60
+ continue;
61
+ return entry;
62
+ }
63
+ return undefined;
64
+ },
65
+ botMessage: (chatId, messageId) => byKey.get(key(chatId, messageId)),
66
+ clear() {
67
+ byKey.clear();
68
+ order.length = 0;
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=bot-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot-messages.js","sourceRoot":"","sources":["../src/bot-messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { Chat, Message } from "@yaebal/core";
2
+ import type { ActorHost, SendText } from "./internal.js";
3
+ import type { UserActor } from "./user-actor.js";
4
+ export type ChatType = "private" | "group" | "supergroup" | "channel";
5
+ export interface CreateChatOptions {
6
+ type: ChatType;
7
+ id?: number;
8
+ title?: string;
9
+ username?: string;
10
+ }
11
+ /** a chat member's tracked status — set via {@link ChatActor.setMembership}. */
12
+ export interface ChatMembership {
13
+ status: string;
14
+ since?: number;
15
+ }
16
+ /**
17
+ * a passive container actors send into — a group/supergroup/channel/private chat. tracks who's
18
+ * currently a member (best-effort — nothing enforces it unless you opt into `strictMembership`
19
+ * via {@link createTestEnv}) and every message dispatched through it.
20
+ */
21
+ export declare class ChatActor {
22
+ readonly id: number;
23
+ readonly type: ChatType;
24
+ readonly title?: string;
25
+ readonly username?: string;
26
+ readonly members: Set<UserActor>;
27
+ readonly messages: unknown[];
28
+ private readonly host;
29
+ private readonly membership;
30
+ constructor(host: ActorHost, options: CreateChatOptions);
31
+ /** reconstruct a `ChatActor` from a raw `Chat` payload seen on an update (e.g. to find the chat a message you didn't create arrived in). not registered with any `TestEnv`. */
32
+ static fromChat(host: ActorHost, chat: Chat): ChatActor;
33
+ /** this chat as a plain {@link Chat} payload, ready to embed in an update. */
34
+ toChat(): Chat;
35
+ /** record (or update) a member's status — used by `strictMembership` and `getChatMember`-style assertions. */
36
+ setMembership(userId: number, membership: ChatMembership): void;
37
+ /** the tracked membership for a user, or `undefined` if never set. */
38
+ membershipOf(user: UserActor): ChatMembership | undefined;
39
+ /**
40
+ * an anonymous channel post — `update.channel_post`, with no `from` (matching real Telegram:
41
+ * channel posts aren't attributed to a user). throws on non-channel chats.
42
+ */
43
+ post(text: SendText): Promise<Message>;
44
+ }
45
+ //# sourceMappingURL=chat-actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-actor.d.ts","sourceRoot":"","sources":["../src/chat-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAID;;;;GAIG;AACH,qBAAa,SAAS;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,iBAAwB;IACxC,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAM;IAElC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqC;gBAEpD,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB;IAQvD,+KAA+K;IAC/K,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,SAAS;IASvD,8EAA8E;IAC9E,MAAM,IAAI,IAAI;IASd,8GAA8G;IAC9G,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI;IAI/D,sEAAsE;IACtE,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,SAAS;IAIzD;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAmB5C"}
@@ -0,0 +1,72 @@
1
+ import { resolveSendText } from "./internal.js";
2
+ import { createUpdate } from "./updates.js";
3
+ let chatIdCounter = -1000;
4
+ /**
5
+ * a passive container actors send into — a group/supergroup/channel/private chat. tracks who's
6
+ * currently a member (best-effort — nothing enforces it unless you opt into `strictMembership`
7
+ * via {@link createTestEnv}) and every message dispatched through it.
8
+ */
9
+ export class ChatActor {
10
+ id;
11
+ type;
12
+ title;
13
+ username;
14
+ members = new Set();
15
+ messages = [];
16
+ host;
17
+ membership = new Map();
18
+ constructor(host, options) {
19
+ this.host = host;
20
+ this.id = options.id ?? chatIdCounter--;
21
+ this.type = options.type;
22
+ this.title = options.title;
23
+ this.username = options.username;
24
+ }
25
+ /** reconstruct a `ChatActor` from a raw `Chat` payload seen on an update (e.g. to find the chat a message you didn't create arrived in). not registered with any `TestEnv`. */
26
+ static fromChat(host, chat) {
27
+ return new ChatActor(host, {
28
+ type: chat.type,
29
+ id: chat.id,
30
+ title: chat.title,
31
+ username: chat.username,
32
+ });
33
+ }
34
+ /** this chat as a plain {@link Chat} payload, ready to embed in an update. */
35
+ toChat() {
36
+ return {
37
+ id: this.id,
38
+ type: this.type,
39
+ ...(this.title !== undefined ? { title: this.title } : {}),
40
+ ...(this.username !== undefined ? { username: this.username } : {}),
41
+ };
42
+ }
43
+ /** record (or update) a member's status — used by `strictMembership` and `getChatMember`-style assertions. */
44
+ setMembership(userId, membership) {
45
+ this.membership.set(userId, membership);
46
+ }
47
+ /** the tracked membership for a user, or `undefined` if never set. */
48
+ membershipOf(user) {
49
+ return this.membership.get(user.id);
50
+ }
51
+ /**
52
+ * an anonymous channel post — `update.channel_post`, with no `from` (matching real Telegram:
53
+ * channel posts aren't attributed to a user). throws on non-channel chats.
54
+ */
55
+ async post(text) {
56
+ if (this.type !== "channel") {
57
+ throw new Error("ChatActor.post(): only channel chats can post — did you mean a user actor?");
58
+ }
59
+ const { text: resolvedText, entities } = resolveSendText(text);
60
+ const message = {
61
+ message_id: this.host.nextMessageId(),
62
+ date: this.host.now(),
63
+ chat: this.toChat(),
64
+ text: resolvedText,
65
+ ...(entities.length ? { entities } : {}),
66
+ };
67
+ this.messages.push(message);
68
+ await this.host.dispatch(createUpdate({ channel_post: message }));
69
+ return message;
70
+ }
71
+ }
72
+ //# sourceMappingURL=chat-actor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-actor.js","sourceRoot":"","sources":["../src/chat-actor.ts"],"names":[],"mappings":"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"}
package/lib/clock.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * a virtual clock: overrides `Date.now`, `setTimeout`/`clearTimeout`, and
3
+ * `setInterval`/`clearInterval` so TTL / retry-delay / debounce code under test
4
+ * can be advanced instantly instead of waited on for real. installs globally
5
+ * for the lifetime of the clock; always `.restore()` when done (or let
6
+ * {@link TestEnv.shutdown} do it for you).
7
+ */
8
+ export interface TestClock {
9
+ /** the clock's current virtual time, in ms since epoch. */
10
+ now(): number;
11
+ /** advance the clock by `ms`, firing every timer whose deadline falls inside the window (in deadline order). intervals re-arm and may fire multiple times. */
12
+ advance(ms: number): Promise<void>;
13
+ /** uninstall — restores the real `Date.now`/`setTimeout`/`setInterval`. */
14
+ restore(): void;
15
+ }
16
+ /**
17
+ * install a virtual clock, overriding the global timer functions. `startAt`
18
+ * defaults to the real current time so unrelated `Date.now()` reads (e.g. log
19
+ * timestamps) stay plausible.
20
+ */
21
+ export declare function installTestClock(startAt?: number): TestClock;
22
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACzB,2DAA2D;IAC3D,GAAG,IAAI,MAAM,CAAC;IACd,8JAA8J;IAC9J,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,2EAA2E;IAC3E,OAAO,IAAI,IAAI,CAAC;CAChB;AAWD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAmB,GAAG,SAAS,CAuFxE"}