yaebal 0.0.1-fake → 0.0.1
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/LICENSE +21 -0
- package/README.md +16 -0
- package/lib/index.d.ts +43 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +100 -0
- package/lib/index.js.map +1 -0
- package/lib/index.test.d.ts +2 -0
- package/lib/index.test.d.ts.map +1 -0
- package/lib/index.test.js +48 -0
- package/lib/index.test.js.map +1 -0
- package/package.json +52 -7
- package/src/index.test.ts +61 -0
- package/src/index.ts +143 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 neverlane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# yaebal
|
|
2
|
+
|
|
3
|
+
yaebal — the batteries-included entry point (the gramio idea). re-exports the
|
|
4
|
+
core engine, the auto-generated contexts, and the common plugins so a bot needs
|
|
5
|
+
a single import. `createBot()` wires the rich per-update contexts (with their
|
|
6
|
+
auto-generated shortcut methods) onto every update via the core context factory.
|
|
7
|
+
|
|
8
|
+
## install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm add yaebal
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
part of [**yaebal**](https://github.com/neverlane/yaebal) — a type-safe, runtime-agnostic Telegram Bot API framework. MIT.
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yaebal — the batteries-included entry point (the gramio idea). re-exports the
|
|
3
|
+
* core engine, the auto-generated contexts, and the common plugins so a bot needs
|
|
4
|
+
* a single import. `createBot()` wires the rich per-update contexts (with their
|
|
5
|
+
* auto-generated shortcut methods) onto every update via the core context factory.
|
|
6
|
+
*/
|
|
7
|
+
export * from "@yaebal/core";
|
|
8
|
+
export * from "@yaebal/contexts";
|
|
9
|
+
export { and, filters, not, or } from "@yaebal/filters";
|
|
10
|
+
export { html, htmlToEntities, md, mdToEntities } from "@yaebal/fmt";
|
|
11
|
+
export { InlineKeyboard, Keyboard } from "@yaebal/keyboard";
|
|
12
|
+
export { callbackData } from "@yaebal/callback-data";
|
|
13
|
+
export { session } from "@yaebal/session";
|
|
14
|
+
export { i18n } from "@yaebal/i18n";
|
|
15
|
+
export { deleteWebhook, serve, setWebhook, webhook } from "@yaebal/web";
|
|
16
|
+
import { type ContextByType } from "@yaebal/contexts";
|
|
17
|
+
import { type BotOptions, Context, Bot as CoreBot, type FilterQuery, type Filtered, type Middleware } from "@yaebal/core";
|
|
18
|
+
type ContextFactory = NonNullable<BotOptions["contextFactory"]>;
|
|
19
|
+
/**
|
|
20
|
+
* build a base {@link Context} and graft the auto-generated shortcut methods
|
|
21
|
+
* (`react`, `editText`, `pin`, …) of the matching per-update context onto it.
|
|
22
|
+
* core's own methods (`send`/`reply`/…) win; the rest are added.
|
|
23
|
+
*/
|
|
24
|
+
export declare const richContext: ContextFactory;
|
|
25
|
+
type QueryHead<Q extends string> = Q extends `${infer H}:${string}` ? H : Q;
|
|
26
|
+
type RichFor<Q extends string> = QueryHead<Q> extends keyof ContextByType ? ContextByType[QueryHead<Q>] : Record<never, never>;
|
|
27
|
+
/**
|
|
28
|
+
* a {@link CoreBot} whose routers type the handler context to the matching
|
|
29
|
+
* per-update generated context — so `ctx.react`, `ctx.editText`, … are typed
|
|
30
|
+
* (not just present at runtime). `on("message:text")` → `MessageContext`,
|
|
31
|
+
* `on("callback_query:data")` → `CallbackQueryContext`, etc.
|
|
32
|
+
*/
|
|
33
|
+
export declare class Bot<C extends Context = Context> extends CoreBot<C> {
|
|
34
|
+
constructor(token: string, options?: BotOptions);
|
|
35
|
+
on<Q extends FilterQuery>(query: Q, ...handlers: Middleware<Filtered<C, Q> & RichFor<Q>>[]): this;
|
|
36
|
+
command(name: string, ...handlers: Middleware<C & {
|
|
37
|
+
command: string;
|
|
38
|
+
args: string[];
|
|
39
|
+
} & ContextByType["message"]>[]): this;
|
|
40
|
+
}
|
|
41
|
+
/** create a bot whose handlers receive the rich auto-generated context — typed and at runtime. */
|
|
42
|
+
export declare function createBot(token: string, options?: BotOptions): Bot;
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAClE,OAAO,EACN,KAAK,UAAU,EACf,OAAO,EACP,GAAG,IAAI,OAAO,EAEd,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AAEtB,KAAK,cAAc,GAAG,WAAW,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC;AA0BhE;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,cA0CzB,CAAC;AAGF,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;AAC5E,KAAK,OAAO,CAAC,CAAC,SAAS,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,aAAa,GACtE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC3B,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAExB;;;;;GAKG;AACH,qBAAa,GAAG,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;gBACnD,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe;IAK1C,EAAE,CAAC,CAAC,SAAS,WAAW,EAChC,KAAK,EAAE,CAAC,EACR,GAAG,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GACpD,IAAI;IAIE,OAAO,CACf,IAAI,EAAE,MAAM,EACZ,GAAG,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAC3F,IAAI;CAMP;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,GAAG,CAEtE"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yaebal — the batteries-included entry point (the gramio idea). re-exports the
|
|
3
|
+
* core engine, the auto-generated contexts, and the common plugins so a bot needs
|
|
4
|
+
* a single import. `createBot()` wires the rich per-update contexts (with their
|
|
5
|
+
* auto-generated shortcut methods) onto every update via the core context factory.
|
|
6
|
+
*/
|
|
7
|
+
export * from "@yaebal/core";
|
|
8
|
+
export * from "@yaebal/contexts";
|
|
9
|
+
export { and, filters, not, or } from "@yaebal/filters";
|
|
10
|
+
export { html, htmlToEntities, md, mdToEntities } from "@yaebal/fmt";
|
|
11
|
+
export { InlineKeyboard, Keyboard } from "@yaebal/keyboard";
|
|
12
|
+
export { callbackData } from "@yaebal/callback-data";
|
|
13
|
+
export { session } from "@yaebal/session";
|
|
14
|
+
export { i18n } from "@yaebal/i18n";
|
|
15
|
+
export { deleteWebhook, serve, setWebhook, webhook } from "@yaebal/web";
|
|
16
|
+
import { contextFor } from "@yaebal/contexts";
|
|
17
|
+
import { Context, Bot as CoreBot, } from "@yaebal/core";
|
|
18
|
+
/**
|
|
19
|
+
* auto-detect the runtime and read a local file into bytes — so `media.path()` just
|
|
20
|
+
* works on node, bun and deno from a single `yaebal` import, no platform package to pick.
|
|
21
|
+
* the `node:fs` import is dynamic AND guarded by the node check, so on edge/web it's
|
|
22
|
+
* never reached (or bundled): there the last branch throws and you send
|
|
23
|
+
* `media.buffer()` / `media.url()` instead. this is the whole "platform layer" — yaebal
|
|
24
|
+
* needs no mtcute-style `setPlatform()` global because Telegram Bot API is just fetch + this.
|
|
25
|
+
*/
|
|
26
|
+
const autoReadFile = async (path) => {
|
|
27
|
+
const g = globalThis;
|
|
28
|
+
if (g.Deno)
|
|
29
|
+
return g.Deno.readFile(path);
|
|
30
|
+
if (g.Bun)
|
|
31
|
+
return g.Bun.file(path).bytes();
|
|
32
|
+
if (g.process?.versions?.node)
|
|
33
|
+
return (await import("node:fs/promises")).readFile(path);
|
|
34
|
+
throw new Error("yaebal: no filesystem in this runtime — send media.buffer()/url() instead of media.path().");
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* build a base {@link Context} and graft the auto-generated shortcut methods
|
|
38
|
+
* (`react`, `editText`, `pin`, …) of the matching per-update context onto it.
|
|
39
|
+
* core's own methods (`send`/`reply`/…) win; the rest are added.
|
|
40
|
+
*/
|
|
41
|
+
export const richContext = (api, update, updateType) => {
|
|
42
|
+
const ctx = new Context({ api, update, updateType });
|
|
43
|
+
const rich = contextFor(updateType, api, update);
|
|
44
|
+
const target = ctx;
|
|
45
|
+
// payload fields — define as own props so they SHADOW Context's getters
|
|
46
|
+
// (plain assignment throws on getter-only props like `chat`)
|
|
47
|
+
for (const [key, value] of Object.entries(rich)) {
|
|
48
|
+
Object.defineProperty(ctx, key, {
|
|
49
|
+
value,
|
|
50
|
+
writable: true,
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// shortcut methods (`react`, `editText`, …) and accessors (`senderId`, `isPM`, …)
|
|
56
|
+
// live UP the generated prototype chain — the immediate prototype is just
|
|
57
|
+
// `{ constructor }`, the sugar sits on its bases. walk the whole chain and graft
|
|
58
|
+
// every member core lacks; core's own methods/getters (`send`, `chat`, …) win, and
|
|
59
|
+
// a more-derived override wins over a base one (we never overwrite an own prop).
|
|
60
|
+
for (let proto = Object.getPrototypeOf(rich); proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
|
|
61
|
+
for (const [name, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
|
|
62
|
+
if (name === "constructor" || name in target)
|
|
63
|
+
continue;
|
|
64
|
+
if (typeof desc.value === "function") {
|
|
65
|
+
target[name] = desc.value.bind(ctx);
|
|
66
|
+
}
|
|
67
|
+
else if (desc.get) {
|
|
68
|
+
Object.defineProperty(ctx, name, {
|
|
69
|
+
get: desc.get.bind(ctx),
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return ctx;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* a {@link CoreBot} whose routers type the handler context to the matching
|
|
80
|
+
* per-update generated context — so `ctx.react`, `ctx.editText`, … are typed
|
|
81
|
+
* (not just present at runtime). `on("message:text")` → `MessageContext`,
|
|
82
|
+
* `on("callback_query:data")` → `CallbackQueryContext`, etc.
|
|
83
|
+
*/
|
|
84
|
+
export class Bot extends CoreBot {
|
|
85
|
+
constructor(token, options = {}) {
|
|
86
|
+
// auto-detecting file reader as the default; an explicit `readFile` still wins
|
|
87
|
+
super(token, { readFile: autoReadFile, ...options });
|
|
88
|
+
}
|
|
89
|
+
on(query, ...handlers) {
|
|
90
|
+
return super.on(query, ...handlers);
|
|
91
|
+
}
|
|
92
|
+
command(name, ...handlers) {
|
|
93
|
+
return super.command(name, ...handlers);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** create a bot whose handlers receive the rich auto-generated context — typed and at runtime. */
|
|
97
|
+
export function createBot(token, options = {}) {
|
|
98
|
+
return new Bot(token, { contextFactory: richContext, ...options });
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { Context, createBot, richContext } from "./index.js";
|
|
4
|
+
const api = {};
|
|
5
|
+
const messageUpdate = {
|
|
6
|
+
update_id: 1,
|
|
7
|
+
message: {
|
|
8
|
+
message_id: 5,
|
|
9
|
+
date: 0,
|
|
10
|
+
chat: { id: 42, type: "private" },
|
|
11
|
+
from: { id: 7, is_bot: false, first_name: "u" },
|
|
12
|
+
text: "hi",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
test("richContext is a real Context with payload fields and shortcut methods", () => {
|
|
16
|
+
const ctx = richContext(api, messageUpdate, "message");
|
|
17
|
+
assert.ok(ctx instanceof Context); // satisfies the middleware's Context contract
|
|
18
|
+
assert.equal(ctx.text, "hi"); // core getter still works
|
|
19
|
+
assert.equal(ctx.message_id, 5); // payload field grafted on
|
|
20
|
+
assert.equal(typeof ctx.react, "function"); // autogen shortcut grafted on
|
|
21
|
+
assert.equal(typeof ctx.send, "function"); // core method preserved
|
|
22
|
+
});
|
|
23
|
+
test("createBot returns a Bot wired with the rich factory", () => {
|
|
24
|
+
const bot = createBot("123:abc");
|
|
25
|
+
assert.equal(typeof bot.handleUpdate, "function");
|
|
26
|
+
assert.equal(typeof bot.start, "function");
|
|
27
|
+
});
|
|
28
|
+
// compile-time proof: the typed routers narrow ctx to the rich per-update context.
|
|
29
|
+
// (this block only builds if the types actually flow — it's the type test.)
|
|
30
|
+
test("typed routers expose the generated shortcuts + narrowed fields", () => {
|
|
31
|
+
const bot = createBot("123:abc");
|
|
32
|
+
bot.on("message:text", (ctx) => {
|
|
33
|
+
const t = ctx.text; // narrowed to string by the filter query
|
|
34
|
+
void t;
|
|
35
|
+
void ctx.react; // MessageContext shortcut — typed, not just runtime
|
|
36
|
+
void ctx.editText;
|
|
37
|
+
});
|
|
38
|
+
bot.on("callback_query:data", (ctx) => {
|
|
39
|
+
void ctx.answer; // CallbackQueryContext shortcut
|
|
40
|
+
});
|
|
41
|
+
bot.command("start", (ctx) => {
|
|
42
|
+
const args = ctx.args;
|
|
43
|
+
void args;
|
|
44
|
+
void ctx.react; // command handlers get the message context
|
|
45
|
+
});
|
|
46
|
+
assert.ok(bot);
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,GAAG,GAAG,EAAW,CAAC;AAExB,MAAM,aAAa,GAAG;IACrB,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE;QACR,UAAU,EAAE,CAAC;QACb,IAAI,EAAE,CAAC;QACP,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QACjC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE;QAC/C,IAAI,EAAE,IAAI;KACV;CACQ,CAAC;AAEX,IAAI,CAAC,wEAAwE,EAAE,GAAG,EAAE;IACnF,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IAEvD,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,OAAO,CAAC,CAAC,CAAC,8CAA8C;IAEjF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,0BAA0B;IACxD,MAAM,CAAC,KAAK,CAAE,GAAwC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,2BAA2B;IAClG,MAAM,CAAC,KAAK,CAAC,OAAQ,GAAoC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,8BAA8B;IAC5G,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,wBAAwB;AACpE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAChE,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAEjC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,mFAAmF;AACnF,4EAA4E;AAC5E,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC3E,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAEjC,GAAG,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAW,GAAG,CAAC,IAAI,CAAC,CAAC,yCAAyC;QAErE,KAAK,CAAC,CAAC;QACP,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,oDAAoD;QACpE,KAAK,GAAG,CAAC,QAAQ,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,GAAG,EAAE,EAAE;QACrC,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,gCAAgC;IAClD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAa,GAAG,CAAC,IAAI,CAAC;QAEhC,KAAK,IAAI,CAAC;QACV,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,2CAA2C;IAC5D,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;AAChB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaebal",
|
|
3
|
-
"version": "0.0.1
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "yaebal — batteries-included Telegram Bot API framework: core + auto-generated contexts + the common plugins, one import.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"import": "./lib/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@yaebal/core": "0.0.1",
|
|
20
|
+
"@yaebal/contexts": "0.0.1",
|
|
21
|
+
"@yaebal/keyboard": "0.0.1",
|
|
22
|
+
"@yaebal/filters": "0.0.1",
|
|
23
|
+
"@yaebal/session": "0.0.1",
|
|
24
|
+
"@yaebal/callback-data": "0.0.1",
|
|
25
|
+
"@yaebal/fmt": "0.0.1",
|
|
26
|
+
"@yaebal/i18n": "0.0.1",
|
|
27
|
+
"@yaebal/web": "0.0.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "latest"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"telegram",
|
|
37
|
+
"telegram-bot",
|
|
38
|
+
"bot-api",
|
|
39
|
+
"framework",
|
|
40
|
+
"typescript",
|
|
41
|
+
"yaebal"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/neverlane/yaebal",
|
|
47
|
+
"directory": "packages/yaebal"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
9
52
|
"scripts": {
|
|
10
|
-
"
|
|
53
|
+
"build": "tsc -p tsconfig.json",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"test": "node --test lib"
|
|
11
56
|
}
|
|
12
57
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { Context, createBot, richContext } from "./index.js";
|
|
4
|
+
|
|
5
|
+
const api = {} as never;
|
|
6
|
+
|
|
7
|
+
const messageUpdate = {
|
|
8
|
+
update_id: 1,
|
|
9
|
+
message: {
|
|
10
|
+
message_id: 5,
|
|
11
|
+
date: 0,
|
|
12
|
+
chat: { id: 42, type: "private" },
|
|
13
|
+
from: { id: 7, is_bot: false, first_name: "u" },
|
|
14
|
+
text: "hi",
|
|
15
|
+
},
|
|
16
|
+
} as never;
|
|
17
|
+
|
|
18
|
+
test("richContext is a real Context with payload fields and shortcut methods", () => {
|
|
19
|
+
const ctx = richContext(api, messageUpdate, "message");
|
|
20
|
+
|
|
21
|
+
assert.ok(ctx instanceof Context); // satisfies the middleware's Context contract
|
|
22
|
+
|
|
23
|
+
assert.equal(ctx.text, "hi"); // core getter still works
|
|
24
|
+
assert.equal((ctx as Context & { message_id: number }).message_id, 5); // payload field grafted on
|
|
25
|
+
assert.equal(typeof (ctx as Context & { react: unknown }).react, "function"); // autogen shortcut grafted on
|
|
26
|
+
assert.equal(typeof ctx.send, "function"); // core method preserved
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("createBot returns a Bot wired with the rich factory", () => {
|
|
30
|
+
const bot = createBot("123:abc");
|
|
31
|
+
|
|
32
|
+
assert.equal(typeof bot.handleUpdate, "function");
|
|
33
|
+
assert.equal(typeof bot.start, "function");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// compile-time proof: the typed routers narrow ctx to the rich per-update context.
|
|
37
|
+
// (this block only builds if the types actually flow — it's the type test.)
|
|
38
|
+
test("typed routers expose the generated shortcuts + narrowed fields", () => {
|
|
39
|
+
const bot = createBot("123:abc");
|
|
40
|
+
|
|
41
|
+
bot.on("message:text", (ctx) => {
|
|
42
|
+
const t: string = ctx.text; // narrowed to string by the filter query
|
|
43
|
+
|
|
44
|
+
void t;
|
|
45
|
+
void ctx.react; // MessageContext shortcut — typed, not just runtime
|
|
46
|
+
void ctx.editText;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
bot.on("callback_query:data", (ctx) => {
|
|
50
|
+
void ctx.answer; // CallbackQueryContext shortcut
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
bot.command("start", (ctx) => {
|
|
54
|
+
const args: string[] = ctx.args;
|
|
55
|
+
|
|
56
|
+
void args;
|
|
57
|
+
void ctx.react; // command handlers get the message context
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.ok(bot);
|
|
61
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yaebal — the batteries-included entry point (the gramio idea). re-exports the
|
|
3
|
+
* core engine, the auto-generated contexts, and the common plugins so a bot needs
|
|
4
|
+
* a single import. `createBot()` wires the rich per-update contexts (with their
|
|
5
|
+
* auto-generated shortcut methods) onto every update via the core context factory.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from "@yaebal/core";
|
|
9
|
+
export * from "@yaebal/contexts";
|
|
10
|
+
export { and, filters, not, or } from "@yaebal/filters";
|
|
11
|
+
export { html, htmlToEntities, md, mdToEntities } from "@yaebal/fmt";
|
|
12
|
+
export { InlineKeyboard, Keyboard } from "@yaebal/keyboard";
|
|
13
|
+
export { callbackData } from "@yaebal/callback-data";
|
|
14
|
+
export { session } from "@yaebal/session";
|
|
15
|
+
export { i18n } from "@yaebal/i18n";
|
|
16
|
+
export { deleteWebhook, serve, setWebhook, webhook } from "@yaebal/web";
|
|
17
|
+
|
|
18
|
+
import { type ContextByType, contextFor } from "@yaebal/contexts";
|
|
19
|
+
import {
|
|
20
|
+
type BotOptions,
|
|
21
|
+
Context,
|
|
22
|
+
Bot as CoreBot,
|
|
23
|
+
type FileReader,
|
|
24
|
+
type FilterQuery,
|
|
25
|
+
type Filtered,
|
|
26
|
+
type Middleware,
|
|
27
|
+
} from "@yaebal/core";
|
|
28
|
+
|
|
29
|
+
type ContextFactory = NonNullable<BotOptions["contextFactory"]>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* auto-detect the runtime and read a local file into bytes — so `media.path()` just
|
|
33
|
+
* works on node, bun and deno from a single `yaebal` import, no platform package to pick.
|
|
34
|
+
* the `node:fs` import is dynamic AND guarded by the node check, so on edge/web it's
|
|
35
|
+
* never reached (or bundled): there the last branch throws and you send
|
|
36
|
+
* `media.buffer()` / `media.url()` instead. this is the whole "platform layer" — yaebal
|
|
37
|
+
* needs no mtcute-style `setPlatform()` global because Telegram Bot API is just fetch + this.
|
|
38
|
+
*/
|
|
39
|
+
const autoReadFile: FileReader = async (path) => {
|
|
40
|
+
const g = globalThis as {
|
|
41
|
+
Deno?: { readFile(p: string): Promise<Uint8Array> };
|
|
42
|
+
Bun?: { file(p: string): { bytes(): Promise<Uint8Array> } };
|
|
43
|
+
process?: { versions?: { node?: string } };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (g.Deno) return g.Deno.readFile(path);
|
|
47
|
+
if (g.Bun) return g.Bun.file(path).bytes();
|
|
48
|
+
if (g.process?.versions?.node) return (await import("node:fs/promises")).readFile(path);
|
|
49
|
+
|
|
50
|
+
throw new Error(
|
|
51
|
+
"yaebal: no filesystem in this runtime — send media.buffer()/url() instead of media.path().",
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* build a base {@link Context} and graft the auto-generated shortcut methods
|
|
57
|
+
* (`react`, `editText`, `pin`, …) of the matching per-update context onto it.
|
|
58
|
+
* core's own methods (`send`/`reply`/…) win; the rest are added.
|
|
59
|
+
*/
|
|
60
|
+
export const richContext: ContextFactory = (api, update, updateType) => {
|
|
61
|
+
const ctx = new Context({ api, update, updateType });
|
|
62
|
+
const rich = contextFor(updateType as keyof ContextByType, api, update);
|
|
63
|
+
const target = ctx as unknown as Record<string, unknown>;
|
|
64
|
+
|
|
65
|
+
// payload fields — define as own props so they SHADOW Context's getters
|
|
66
|
+
// (plain assignment throws on getter-only props like `chat`)
|
|
67
|
+
for (const [key, value] of Object.entries(rich)) {
|
|
68
|
+
Object.defineProperty(ctx, key, {
|
|
69
|
+
value,
|
|
70
|
+
writable: true,
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// shortcut methods (`react`, `editText`, …) and accessors (`senderId`, `isPM`, …)
|
|
77
|
+
// live UP the generated prototype chain — the immediate prototype is just
|
|
78
|
+
// `{ constructor }`, the sugar sits on its bases. walk the whole chain and graft
|
|
79
|
+
// every member core lacks; core's own methods/getters (`send`, `chat`, …) win, and
|
|
80
|
+
// a more-derived override wins over a base one (we never overwrite an own prop).
|
|
81
|
+
for (
|
|
82
|
+
let proto = Object.getPrototypeOf(rich);
|
|
83
|
+
proto && proto !== Object.prototype;
|
|
84
|
+
proto = Object.getPrototypeOf(proto)
|
|
85
|
+
) {
|
|
86
|
+
for (const [name, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
|
|
87
|
+
if (name === "constructor" || name in target) continue;
|
|
88
|
+
|
|
89
|
+
if (typeof desc.value === "function") {
|
|
90
|
+
target[name] = (desc.value as (...args: unknown[]) => unknown).bind(ctx);
|
|
91
|
+
} else if (desc.get) {
|
|
92
|
+
Object.defineProperty(ctx, name, {
|
|
93
|
+
get: desc.get.bind(ctx),
|
|
94
|
+
enumerable: true,
|
|
95
|
+
configurable: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return ctx;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// map a filter query to the matching generated context (the part before `:`).
|
|
105
|
+
type QueryHead<Q extends string> = Q extends `${infer H}:${string}` ? H : Q;
|
|
106
|
+
type RichFor<Q extends string> = QueryHead<Q> extends keyof ContextByType
|
|
107
|
+
? ContextByType[QueryHead<Q>]
|
|
108
|
+
: Record<never, never>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* a {@link CoreBot} whose routers type the handler context to the matching
|
|
112
|
+
* per-update generated context — so `ctx.react`, `ctx.editText`, … are typed
|
|
113
|
+
* (not just present at runtime). `on("message:text")` → `MessageContext`,
|
|
114
|
+
* `on("callback_query:data")` → `CallbackQueryContext`, etc.
|
|
115
|
+
*/
|
|
116
|
+
export class Bot<C extends Context = Context> extends CoreBot<C> {
|
|
117
|
+
constructor(token: string, options: BotOptions = {}) {
|
|
118
|
+
// auto-detecting file reader as the default; an explicit `readFile` still wins
|
|
119
|
+
super(token, { readFile: autoReadFile, ...options });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override on<Q extends FilterQuery>(
|
|
123
|
+
query: Q,
|
|
124
|
+
...handlers: Middleware<Filtered<C, Q> & RichFor<Q>>[]
|
|
125
|
+
): this {
|
|
126
|
+
return super.on(query, ...(handlers as unknown as Middleware<Filtered<C, Q>>[]));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override command(
|
|
130
|
+
name: string,
|
|
131
|
+
...handlers: Middleware<C & { command: string; args: string[] } & ContextByType["message"]>[]
|
|
132
|
+
): this {
|
|
133
|
+
return super.command(
|
|
134
|
+
name,
|
|
135
|
+
...(handlers as unknown as Middleware<C & { command: string; args: string[] }>[]),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** create a bot whose handlers receive the rich auto-generated context — typed and at runtime. */
|
|
141
|
+
export function createBot(token: string, options: BotOptions = {}): Bot {
|
|
142
|
+
return new Bot(token, { contextFactory: richContext, ...options });
|
|
143
|
+
}
|